forked from troelsbjerre/Bottleneck
-
Notifications
You must be signed in to change notification settings - Fork 0
/
control.lua
392 lines (352 loc) · 14.6 KB
/
control.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
-------------------------------------------------------------------------------
--[[Bottleneck]]--
-------------------------------------------------------------------------------
local bn_signals_per_tick = settings.global["bottleneck-signals-per-tick"].value
--[[ code modified from AutoDeconstruct mod by mindmix https://mods.factorio.com/mods/mindmix ]]
local function check_drill_depleted(data)
local drill = data.entity
local position = drill.position
local range = drill.prototype.mining_drill_radius
local top_left = {x = position.x - range, y = position.y - range}
local bottom_right = {x = position.x + range, y = position.y + range}
local resources = drill.surface.find_entities_filtered{area={top_left, bottom_right}, type='resource'}
for _, resource in pairs(resources) do
if resource.prototype.resource_category == 'basic-solid' and resource.amount > 0 then
return false
end
end
data.drill_depleted = true
return true
end
--Api request sent to make this faster
--https://forums.factorio.com/viewtopic.php?f=28&t=48100
local function has_fluid_output_available(entity)
local fluidbox = entity.fluidbox
if entity.recipe and fluidbox and #fluidbox > 0 then
for _, product in pairs(entity.recipe.products) do
if product.type == 'fluid' then
for i = 1, #fluidbox do
local fluid = fluidbox[i]
if fluid and (fluid.type == product.name) and (fluid.amount > 0) then
return true
end
end
end
end
end
end
local LIGHT = {
off = 1, green = 2, red = 3, yellow = 4, blue = 5, redx = 6, yellowmin = 7,
offsmall = 8, greensmall = 9, redsmall = 10, yellowsmall = 11,
bluesmall = 12, redxsmall = 13, yellowminsmall = 14,
}
local STATES = {
OFF = 1, RUNNING = 2, STOPPED = 3, FULL = 4,
}
local STYLE = {
LIGHT.off,
LIGHT[settings.global["bottleneck-show-running-as"].value],
LIGHT[settings.global["bottleneck-show-stopped-as"].value],
LIGHT[settings.global["bottleneck-show-full-as"].value],
}
--Faster to just change the color than it is to check it first.
local function change_signal(data, status)
data.signal.graphics_variation = STYLE[status] or 1
data.status = status or STATES.OFF
end
--[[ Remove the LIGHT]]
local function remove_signal(event)
local entity = event.entity
local index = entity.unit_number
local overlays = global.overlays
local data = overlays[index]
if data then
local signal = data.signal
if signal and signal.valid then
signal.destroy()
end
overlays[index] = nil
end
end
--[[ Calculates bottom center of the entity to place bottleneck there ]]
local function get_signal_position_from(entity)
local left_top = entity.prototype.selection_box.left_top
local right_bottom = entity.prototype.selection_box.right_bottom
--Calculating center of the selection box
local center = (left_top.x + right_bottom.x) / 2
local width = math.abs(left_top.x) + right_bottom.x
-- Set Shift here if needed, The offset looks better as it doesn't cover up fluid input information
-- Ignore shift for 1 tile entities
local x = (width > 1.25 and center - 0.5) or center
local y = right_bottom.y
--Calculating bottom center of the selection box
return {x = entity.position.x + x, y = entity.position.y + y}
end
local function new_signal(entity, variation)
local signal = entity.surface.create_entity{name = "bottleneck-stoplight", position = get_signal_position_from(entity), force = entity.force}
signal.graphics_variation = (global.show_bottlenecks < 1 and LIGHT["off"]) or variation or LIGHT["red"]
signal.destructible = false
return signal
end
local function entity_moved(event, data)
data = data or global.overlays[event.moved_entity.unit_number]
if data then
if data.signal and data.signal.valid then
data.drill_depleted = false
local position = get_signal_position_from(event.moved_entity)
data.signal.teleport(position)
end
end
end
local update = {}
function update.drill(data)
if not data.drill_depleted then
local entity = data.entity
local progress = data.progress
if (entity.energy == 0) or (entity.mining_target == nil and check_drill_depleted(data)) then
change_signal(data, STATES.STOPPED)
elseif (entity.mining_progress == progress) then
local fluidbox = entity.fluidbox
if #fluidbox > 0 then
local fluid = fluidbox[1]
if fluid and fluid.amount > 0 then
change_signal(data, STATES.FULL)
else
change_signal(data, STATES.STOPPED)
end
else
change_signal(data, STATES.FULL)
end
else
change_signal(data, STATES.RUNNING)
data.progress = entity.mining_progress
end
end
end
function update.machine(data)
local entity = data.entity
if entity.energy == 0 then
change_signal(data, STATES.STOPPED)
elseif entity.is_crafting() and (entity.crafting_progress < 1) and (entity.bonus_progress < 1) then
change_signal(data, STATES.RUNNING)
elseif (entity.crafting_progress >= 1) or (entity.bonus_progress >= 1) or (not entity.get_output_inventory().is_empty()) or (has_fluid_output_available(entity)) then
change_signal(data, STATES.FULL)
else
change_signal(data, STATES.STOPPED)
end
end
function update.furnace(data)
local entity = data.entity
if entity.energy == 0 then
change_signal(data, STATES.STOPPED)
elseif entity.is_crafting() and (entity.crafting_progress < 1) and (entity.bonus_progress < 1) then
change_signal(data, STATES.RUNNING)
elseif (entity.crafting_progress >= 1) or (entity.bonus_progress >= 1) or (not entity.get_output_inventory().is_empty()) or (has_fluid_output_available(entity)) then
change_signal(data, STATES.FULL)
else
change_signal(data, STATES.STOPPED)
end
end
--[[ A function that is called whenever an entity is built (both by player and by robots) ]]--
local function built(event)
local entity = event.created_entity
local data
-- If the entity that's been built is an assembly machine or a furnace...
if entity.type == "assembling-machine" then
data = { update = "machine" }
elseif entity.type == "furnace" then
data = { update = "furnace" }
elseif entity.type == "mining-drill" and entity.name ~= "factory-port-marker" then
data = { update = "drill" }
end
if data then
data.entity = entity
data.signal = new_signal(entity)
data.status = STATES.STOPPED
--update[data.update](data)
global.overlays[entity.unit_number] = data
-- if we are in the process of removing LIGHTs, we need to restart
-- that, since inserting into the overlays table may mess up the
-- iteration order.
if global.show_bottlenecks == -1 then
global.update_index = nil
end
end
end
local function rebuild_overlays()
--[[Setup the global overlays table This table contains the machine entity, the signal entity and the freeze variable]]--
global.overlays = {}
global.update_index = nil
--game.print("Bottleneck: Rebuilding data from scratch")
--[[Find all assembling machines on the map. Check each surface]]--
for _, surface in pairs(game.surfaces) do
--find-entities-filtered with no area argument scans for all entities in loaded chunks and should
--be more effiecent then scanning through all chunks like in previous version
--[[destroy any existing bottleneck-signals]]--
for _, stoplight in pairs(surface.find_entities_filtered{name="bottleneck-stoplight"}) do
stoplight.destroy()
end
--[[Find all assembling machines within the bounds, and pretend that they were just built]]--
for _, am in pairs(surface.find_entities_filtered{type="assembling-machine"}) do
built({created_entity = am})
end
--[[Find all furnaces within the bounds, and pretend that they were just built]]--
for _, am in pairs(surface.find_entities_filtered{type="furnace"}) do
built({created_entity = am})
end
--[[Find all mining-drills within the bounds, and pretend that they were just built]]--
for _, am in pairs(surface.find_entities_filtered{type="mining-drill"}) do
built({created_entity = am})
end
end
end
local function on_tick()
local show_bottlenecks = global.show_bottlenecks
if show_bottlenecks ~= 0 then
local next = next --very slight perfomance improvment
local signals_per_tick = bn_signals_per_tick
local overlays = global.overlays
local index = global.update_index
local data
--check for existing data at index
if index and overlays[index] then
data = overlays[index]
else
index, data = next(overlays, index)
end
local numiter = 0
while index and (numiter < signals_per_tick) do
local entity = data.entity
if entity.valid then -- if entity is valid, update it, otherwise remove the signal and the associated data
if data.signal.valid then
if show_bottlenecks > 0 then
update[data.update](data)
else
change_signal(data, STATES.OFF)
end
else -- Rebuild the icon something broke it!
data.signal = new_signal(entity)
end
else -- Machine is gone
if data.signal.valid then
data.signal.destroy() -- Signal is there; remove it
end
overlays[index] = nil -- forget about the machine
end
numiter = numiter + 1
index, data = next(overlays, index)
end
global.update_index = index
-- if we have reached the end of the list (i.e., have removed all LIGHTs),
-- pause updating until enabled by hotkey next
if not index and show_bottlenecks <= 0 then
global.show_bottlenecks = 0
--We have cycled everything to off, disable the tick handler
script.on_event(defines.events.on_tick, nil)
end
end
end
local function update_settings(event)
if event.setting == "bottleneck-signals_per_tick" then
bn_signals_per_tick = settings.global["bottleneck-signals-per-tick"].value
end
if event.setting == "bottleneck-show-running-as" then
STYLE[STATES.RUNNING] = LIGHT[settings.global["bottleneck-show-running-as"].value]
end
if event.setting == "bottleneck-show-stopped-as" then
STYLE[STATES.STOPPED] = LIGHT[settings.global["bottleneck-show-stopped-as"].value]
end
if event.setting == "bottleneck-show-full-as" then
STYLE[STATES.FULL] = LIGHT[settings.global["bottleneck-show-full-as"].value]
end
end
script.on_event(defines.events.on_runtime_mod_setting_changed, update_settings)
-------------------------------------------------------------------------------
--[[Init Events]]
local function register_conditional_events()
if remote.interfaces["picker"] and remote.interfaces["picker"]["dolly_moved_entity_id"] then
script.on_event(remote.call("picker", "dolly_moved_entity_id"), entity_moved)
end
if global.show_bottlenecks ~= 0 then
--Register the tick handler if we are showing bottlenecks
script.on_event(defines.events.on_tick, on_tick)
end
end
local function init()
global.overlays = {}
global.show_bottlenecks = 1
--register the tick handler if we are showing bottlenecks
if global.show_bottlenecks then
script.on_event(defines.events.on_tick, on_tick)
end
rebuild_overlays()
register_conditional_events()
end
local function on_load()
register_conditional_events()
end
local function on_configuration_changed(event)
--Any MOD has been changed/added/removed, including base game updates.
if event.mod_changes then
--This mod has changed
local changes = event.mod_changes["Bottleneck"]
if changes then -- THIS Mod has changed
game.print("Bottleneck: Updated from ".. tostring(changes.old_version) .. " to " .. tostring(changes.new_version))
global.show_bottlenecks = global.show_bottlenecks or 1
--Clean up old variables
global.lights_per_tick = nil
global.signals_per_tick = nil
global.showbottlenecks = nil
global.output_idle_signal = nil
global.high_contrast = nil
end
global.overlays = {}
rebuild_overlays()
end
end
--[[ Hotkey ]]--
local function on_hotkey(event)
local player = game.players[event.player_index]
if not player.admin then
player.print('Bottleneck: You do not have privileges to toggle bottleneck')
return
end
global.update_index = nil
if global.show_bottlenecks == 1 then
global.show_bottlenecks = -1
else
global.show_bottlenecks = 1
end
--Toggling the setting doesn't disable right way, make sure the handler gets
--reenabled to toggle colors to their correct values.
script.on_event(defines.events.on_tick, on_tick)
end
--[[ Setup event handlers ]]--
script.on_init(init)
script.on_configuration_changed(on_configuration_changed)
script.on_load(on_load)
local e=defines.events
local remove_events = {e.on_player_mined_entity, e.on_robot_pre_mined, e.on_entity_died}
local add_events = {e.on_built_entity, e.on_robot_built_entity}
script.on_event(remove_events, remove_signal)
script.on_event(add_events, built)
script.on_event("bottleneck-hotkey", on_hotkey)
--[[ Setup remote interface]]--
local interface = {}
--is_enabled - return show_bottlenecks
interface.enabled = function() return global.show_bottlenecks end
--print the global to a file
interface.print_global = function () game.write_file("Bottleneck/global.lua", serpent.block(global, {nocode=true, comment=false})) end
--rebuild all icons
interface.rebuild = rebuild_overlays
--allow other mods to interact with bottleneck
interface.entity_moved = entity_moved
interface.get_lights = function() return LIGHT end
interface.get_states = function() return STATES end
interface.new_signal = new_signal
interface.change_signal = change_signal --function(data, color) change_signal(signal, color) end
--get a place position for a signal
interface.get_position_for_signal = get_signal_position_from
--get the signal data associated with an entity
interface.get_signal_data = function(unit_number) return global.overlays[unit_number] end
remote.add_interface("Bottleneck", interface)