show-buildable/control.lua

changeset 14
c26d4dd2af9b
equal deleted inserted replaced
13:826df96c3720 14:c26d4dd2af9b
1 require"sb-util"
2
3 ---@param player LuaPlayer
4 local function get_entity_player_wants_to_place(player)
5 local item_name = nil
6 if player.cursor_stack.valid_for_read
7 then
8 item_name = player.cursor_stack.name
9 elseif player.cursor_ghost
10 then
11 item_name = player.cursor_ghost.name.name
12 end
13 local entity_name = nil
14 local unsupported_types = {
15 ["straight-rail"] = true,
16 ["rail-ramp"] = true,
17 ["rail-support"] = true,
18 ["rail-signal"] = true,
19 ["rail-chain-signal"] = true,
20 ["train-stop"] = true,
21 ["locomotive"] = true,
22 ["cargo-wagon"] = true,
23 ["fluid-wagon"] = true,
24 ["offshore-pump"] = true,
25 ["car"] = true,
26 ["spider-vehicle"] = true,
27 }
28 if item_name
29 then
30 item_proto = prototypes.item[item_name]
31 if item_proto and item_proto.place_result
32 and not item_proto.place_result.flags["placeable-off-grid"]
33 and not unsupported_types[item_proto.place_result.type]
34 then
35 entity_name = item_proto.place_result.name
36 end
37 end
38 return entity_name
39 end
40
41 -- where to try place the entity inside the grid
42 -- 1×1 entities require 0.5 offset, 2×2 require 1 offset,
43 -- 3×3 entities require 0.5 again, 4×4 require 1.
44 -- and so on...
45 ---@param collision_box data.BoundingBox
46 local function offset_x(collision_box)
47 local x = collision_box.left_top.x
48 local oy = ((x%1) - (x % 0.5)) or 1
49 return oy > 0 and oy or 1
50 end
51
52 ---@param collision_box data.BoundingBox
53 local function offset_y(collision_box)
54 local y = collision_box.left_top.y
55 local oy = ((y%1) - (y % 0.5)) or 1
56 return oy > 0 and oy or 1
57 end
58
59 ---@param proto LuaEntityPrototype
60 --- can x be rotated???
61 local function can_be_rotated(proto)
62 return proto.supports_direction and proto.collision_box.left_top.x ~= proto.collision_box.left_top.y
63 end
64
65 local function collision_tester(entity_name)
66 if entity_name and prototypes.entity["collision-tester-"..entity_name]
67 then
68 return "collision-tester-"..entity_name
69 else
70 return entity_name
71 end
72 end
73
74 ---@param a table
75 ---@param b any
76 local function find(a, b)
77 for k, v in pairs(a)
78 do
79 if v == b
80 then
81 return k
82 end
83 end
84 end
85
86 local function join(sep, x)
87 result = ""
88 local i = 0
89 for k, v in pairs(x)
90 do
91 if i > 0
92 then
93 result = result..sep
94 end
95 result = result..v
96 i = i + 1
97 end
98 return result
99 end
100
101 ---@param a MapPosition
102 ---@param b MapPosition
103 local function manh(a, b)
104 return math.abs(b.x - a.x) + math.abs(b.y - a.y)
105 end
106
107 ---@param surface string
108 ---@param pos MapPosition
109 ---@param proto LuaEntityPrototype
110 function resources_test(surface, pos, proto)
111 if storage.num_memoized_resource_tests == nil or storage.num_memoized_resource_tests > 1000
112 then
113 storage.num_memoized_resource_tests = 0
114 ---@type table<string, table<number, table<number, table<string, boolean>>>>
115 storage.memoized_resource_tests = {}
116 end
117 if not storage.memoized_resource_tests[surface]
118 then
119 storage.memoized_resource_tests[surface] = {}
120 end
121 if not storage.memoized_resource_tests[surface][pos.x]
122 then
123 storage.memoized_resource_tests[surface][pos.x] = {}
124 end
125 if not storage.memoized_resource_tests[surface][pos.x][pos.y]
126 then
127 storage.memoized_resource_tests[surface][pos.x][pos.y] = {}
128 end
129 local arr = storage.memoized_resource_tests[surface][pos.x][pos.y]
130 if arr[proto.name] ~= nil
131 then
132 return arr[proto.name]
133 else
134 -- check resources
135 local resources = {}
136 for _, resource in pairs(prototypes.entity)
137 do
138 if resource.type == "resource"
139 and (
140 resource.resource_category == proto.resource_category
141 or (proto.resource_categories or {})[resource.resource_category]
142 )
143 then
144 table.insert(resources, resource.name)
145 end
146 end
147 local r = proto.mining_drill_radius
148 local resources_found = game.surfaces[surface].find_entities_filtered{
149 area = {{pos.x - r, pos.y - r}, {pos.x + r, pos.y + r}},
150 type = "resource",
151 name = resources,
152 limit = 1
153 }
154 -- special extra test for pumpjacks because those need to be placed
155 -- directly on top of the resource
156 local found = (#resources_found > 0)
157 if found and proto.mining_drill_radius < 1
158 and manh(resources_found[1].position, pos) > proto.mining_drill_radius
159 then
160 found = false
161 end
162 arr[proto.name] = found
163 return found
164 end
165 end
166
167 script.on_nth_tick(30, function()
168 for _, player in pairs(game.players)
169 do
170 if player.game_view_settings.show_entity_info
171 then
172 local entity_name = get_entity_player_wants_to_place(player)
173 local collision_box_table = {}
174 local scale = 1
175 if entity_name ~= nil
176 then
177 local proto = prototypes.entity[entity_name]
178 scale = 1
179 if proto.type == "cargo-bay" or proto.type == "cargo-landing-pad"
180 then
181 scale = 2
182 end
183 local scaled_ceil = function(x) return math.ceil(x / scale) * scale end
184 local rotated_box = {
185 left_top = {
186 x = proto.collision_box.left_top.y,
187 y = proto.collision_box.left_top.x,
188 },
189 right_bottom = {
190 x = proto.collision_box.right_bottom.y,
191 y = proto.collision_box.right_bottom.x,
192 },
193 }
194 ---@param x number
195 ---@param y number
196 ---@param direction defines.direction
197 ---@param collision_box data.BoundingBox
198 local function test_placement(x, y, direction, collision_box)
199 local pos = {
200 x = x + offset_x(collision_box) * scale,
201 y = y + offset_y(collision_box) * scale
202 }
203 local can_build = player.can_place_entity{
204 name = collision_tester(entity_name),
205 position = pos,
206 direction = direction,
207 terrain_building_size = 1,
208 }
209 if can_build and proto.type == "mining-drill" and not resources_test(player.surface.name, pos, proto)
210 then
211 can_build = false
212 end
213 if can_build
214 then
215 local xi = scaled_ceil(collision_box.left_top.x)
216 local xj = scaled_ceil(collision_box.right_bottom.x + 0.5*scale)
217 local yj = scaled_ceil(collision_box.right_bottom.y + 0.5*scale)
218 while xi < xj
219 do
220 local yi = scaled_ceil(collision_box.left_top.y)
221 while yi < yj
222 do
223 if not collision_box_table[x + xi]
224 then
225 collision_box_table[x + xi] = {}
226 end
227 collision_box_table[x + xi][y + yi] = true
228 yi = yi + scale
229 end
230 xi = xi + scale
231 end
232 else
233 if collision_box_table[x][y] == nil
234 then
235 collision_box_table[x][y] = false
236 end
237 end
238 end
239 local x0 = scaled_ceil(player.position.x)
240 local y0 = scaled_ceil(player.position.y)
241 local x = x0 - 12*scale
242 local xn = x0 + 11*scale
243 local yn = y0 + 11*scale
244 while x <= xn
245 do
246 if not collision_box_table[x]
247 then
248 collision_box_table[x] = {}
249 end
250 local y = y0 - 12*scale
251 while y <= yn
252 do
253 local collision_box = proto.collision_box
254 test_placement(x, y, defines.direction.north, collision_box)
255 if proto.type == "asteroid-collector"
256 then
257 test_placement(x, y, defines.direction.east, rotated_box)
258 test_placement(x, y, defines.direction.west, rotated_box)
259 test_placement(x, y, defines.direction.south, collision_box)
260 elseif can_be_rotated(proto)
261 then
262 test_placement(x, y, defines.direction.east, rotated_box)
263 end
264 y = y + scale
265 end
266 x = x + scale
267 end
268 end
269 local function red(a) return {r=a, g=0, b=0, a=a } end
270 local function green(a) return {r=0, g=a, b=0, a=a } end
271 for x, k in pairs(collision_box_table)
272 do
273 for y, can_build in pairs(k)
274 do
275 ---@return LuaRendering.draw_rectangle_param
276 local parms = function(a)
277 return {
278 surface = player.surface,
279 color = (can_build and green or red)(a),
280 filled = true,
281 left_top = {x = x, y = y },
282 right_bottom = { x = x + scale - 0.05, y = y + scale - 0.05 },
283 time_to_live = 30,
284 players = {player},
285 only_in_alt_mode = true,
286 }
287 end
288 rendering.draw_rectangle(parms (0.2))
289 local parms_2 = parms(0.2)
290 parms_2.draw_on_ground = true
291 rendering.draw_rectangle(parms_2)
292 end
293 end
294 end
295 end
296 end)

mercurial