|
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) |