-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcontrol.lua
422 lines (368 loc) · 14.9 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
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
local SIGNAL_DISPATCH = {type="virtual", name="dispatcher-station"}
-- Initiate global variables when activating the mod
script.on_init(function()
-- Store all the train awaiting dispatch
-- (we need to keep track of these trains in order to dispatch a train when a dispatch signal is sent)
global.awaiting_dispatch = {}
-- Store all the train that have been dispatched
-- (we need to keep track of this in order to be able to reset the train schedule when it arrives at its destination)
global.dispatched = {}
-- Store all the stations
-- (we need it for performance reasons)
global.stations = {}
global.debug = false
end)
-- When configuration is changed (new mod version, etc.)
script.on_configuration_changed(function(data)
if data.mod_changes and data.mod_changes.Dispatcher then
local old_version = data.mod_changes.Dispatcher.old_version
local new_version = data.mod_changes.Dispatcher.new_version
-- Mod version upgraded
if old_version then
if old_version < "1.0.2" then
global.debug = false
end
end
-- Build the list of stations on the map
build_list_stations()
-- Scrub train list to remove ones that don't exist anymore
scrub_trains()
end
end)
-- Add new station to global.stations if it meets our criteria
function add_station(entity)
local name = entity.backer_name
local id = entity.unit_number
if entity.name == "train-stop-dispatcher" or name:match("%.[123456789]%d*$") then
if not global.stations[name] then
global.stations[name] = {}
global.stations[name][id] = entity
debug("Added first station named: ", name)
else
global.stations[name][id] = entity
debug("Added station: ", name)
end
else
debug("Ignoring new station: ", name)
end
end
-- Remove station from global.stations if it is in the list
function remove_station(entity, old_name)
local name = old_name
if not name then name = entity.backer_name end
local id = entity.unit_number
if global.stations[name] then
if global.stations[name][id] then
global.stations[name][id] = nil
if table_size(global.stations[name]) == 0 then
global.stations[name] = nil
debug("Removed last station named: ", name)
else
debug("Removed station: ", name)
end
end
end
end
-- Add stations when built/revived
function entity_built(event)
local entity = event.created_entity
if not entity then entity = event.entity end
if entity and entity.valid then
if entity.type == "train-stop" then
add_station(entity)
end
end
end
script.on_event({ defines.events.on_built_entity,
defines.events.on_robot_built_entity,
defines.events.script_raised_built,
defines.events.script_raised_revive },
entity_built)
-- Remove station when mined/destroyed
function entity_removed(event)
local entity = event.entity
if entity and entity.valid then
if entity.type == "train-stop" then
remove_station(entity)
end
end
end
script.on_event({ defines.events.on_player_mined_entity,
defines.events.on_robot_mined_entity,
defines.events.on_entity_died,
defines.events.script_raised_destroy },
entity_removed)
-- Update station when renamed by player or script
function entity_renamed(event)
local entity = event.entity
if entity and entity.valid then
if entity.type == "train-stop" then
remove_station(entity, event.old_name)
add_station(entity)
end
end
end
script.on_event(defines.events.on_entity_renamed, entity_renamed)
-- Build list of stations
function build_list_stations()
global.stations = {}
local stations = game.surfaces["nauvis"].find_entities_filtered{type= "train-stop"}
for _, station in pairs(stations) do
add_station(station)
end
debug("Stations list rebuilt")
end
-- Scrub list of trains
function scrub_trains()
for id,ad in pairs(global.awaiting_dispatch) do
if not(ad.train and ad.train.valid) then
global.awaiting_dispatch[id] = nil
debug("Scrubbed train " .. id .. " from Awaiting Dispatch list.")
end
end
for id,d in pairs(global.dispatched) do
if not(d.train and d.train.valid) then
global.dispatched[id] = nil
debug("Scrubbed train " .. id .. " from Dispatched list.")
end
end
end
-- Track train state change
function train_changed_state(event)
local id = event.train.id
-- A train that is awaiting dispatch cannot change state
if global.awaiting_dispatch[id] ~= nil then
-- If the train mode has been set to manual mode, then we need to reset the train schedule
if event.train.manual_mode then
local schedule = global.awaiting_dispatch[id].schedule
event.train.schedule = schedule
debug("Train #", id, " set to manual mode while awaiting dispatch: schedule reset")
else
debug("Train #", id, " schedule changed while awaiting dispatch: train not awaiting dispatch anymore but schedule not reset")
end
global.awaiting_dispatch[id] = nil
end
-- Ensure that dispatched train are going to the chosen destination
if global.dispatched[id] ~= nil and (event.train.schedule == nil or global.dispatched[id].current ~= event.train.schedule.current) then
reset_station(id)
debug("Train #", id, " is no longer being dispatched: schedule reset")
end
-- When a train arrives at a dispatcher
if event.train.state == defines.train_state.wait_station and event.train.station ~= nil and event.train.station.name == "train-stop-dispatcher" then
-- Add the train to the global variable storing all the trains awaiting dispatch
global.awaiting_dispatch[id] = {train=event.train, station=event.train.station, schedule=event.train.schedule}
-- Change the train schedule so that the train stays at the station
event.train.schedule = {current=1, records={{station=event.train.station.backer_name, wait_conditions={{type="circuit", compare_type="or", condition={}}}}}}
debug("Train #", id, " has arrived to dispatcher '", event.train.station.backer_name, "': awaiting dispatch")
end
end
script.on_event(defines.events.on_train_changed_state, train_changed_state)
-- Track uncoupled trains (because the train id changes)
function train_created(event)
if event.old_train_id_1 and event.old_train_id_2 then
ad = nil
if global.awaiting_dispatch[event.old_train_id_1] then
ad = global.awaiting_dispatch[event.old_train_id_1]
elseif global.awaiting_dispatch[event.old_train_id_2] then
ad = global.awaiting_dispatch[event.old_train_id_2]
end
if ad then
if event.train.schedule then
event.train.schedule = ad.schedule
event.train.manual_mode = false
global.awaiting_dispatch[event.train.id] = nil
debug("Train #", event.old_train_id_1, " and #", event.old_train_id_2, " merged while awaiting dispatch: new train #", event.train.id, " schedule reset, and mode set to automatic")
else
debug("Train #", event.old_train_id_1, " and #", event.old_train_id_2, " merged while awaiting dispatch: new train #", event.train.id, " set to manual because it has no schedule")
end
global.awaiting_dispatch[event.old_train_id_2] = nil
global.awaiting_dispatch[event.old_train_id_1] = nil
end
d = nil
schedule = nil
if global.dispatched[event.old_train_id_1] then
d = global.dispatched[event.old_train_id_1]
elseif global.dispatched[event.old_train_id_2] then
d = global.dispatched[event.old_train_id_2]
end
if d then
if event.train.schedule then
global.dispatched[event.train.id] = {train=event.train, station=d.station, current=d.current}
event.train.manual_mode = false
debug("Train #", event.old_train_id_1, " and #", event.old_train_id_2, " merged while being dispatched: new train #", event.train.id, " is being dispatched")
else
debug("Train #", event.old_train_id_1, " and #", event.old_train_id_2, " merged while being dispatched: new train #", event.train.id, " is no longer dispatched because it has no schedule")
end
global.dispatched[event.old_train_id_1] = nil
global.dispatched[event.old_train_id_2] = nil
end
elseif event.old_train_id_1 then
if global.awaiting_dispatch[event.old_train_id_1] then
ad = global.awaiting_dispatch[event.old_train_id_1]
event.train.schedule = ad.schedule
if has_locos(event.train) then
event.train.manual_mode = false
global.dispatched[event.old_train_id_1] = nil
debug("Train #", event.old_train_id_1, " was split to create train #", event.train.id, " while awaiting dispatch: train schedule reset, and mode set to automatic")
else
debug("Train #", event.old_train_id_1, " was split to create train #", event.train.id, " while awaiting dispatch: train schedule reset, and mode set to manual because it has no locomotives")
end
end
if global.dispatched[event.old_train_id_1] then
if has_locos(event.train) then
d = global.dispatched[event.old_train_id_1]
global.dispatched[event.train.id] = {train=event.train, station=d.station, current=d.current}
event.train.manual_mode = false
global.dispatched[event.old_train_id_1] = nil
debug("Train #", event.old_train_id_1, " was split to create train #", event.train.id, " while being dispatched: train schedule reset, and mode set to automatic")
else
debug("Train #", event.old_train_id_1, " was split to create train #", event.train.id, " while being dispatched: no longer dispatched, and mode set to manual because it has no locomotives")
end
end
end
end
script.on_event(defines.events.on_train_created, train_created)
-- Executed every tick
function tick()
for i,v in pairs(global.awaiting_dispatch) do
-- Ensure that the train still exists
if not v.train.valid then
global.awaiting_dispatch[i] = nil
debug("Train #", i, " no longer exists: removed from awaiting dispatch list")
-- Ensure that the dispatcher still exists, if not reset the train schedule
elseif not v.station.valid then
v.train.schedule = v.schedule
global.awaiting_dispatch[i] = nil
debug("Train #", v.train.id, " is awaiting at a dispatcher that no longer exists: schedule reset and train removed from awaiting dispatch list")
else
-- Get the dispatch signal at the dispatcher
local signal = v.station.get_merged_signal(SIGNAL_DISPATCH)
if signal ~= nil then
local name = v.station.backer_name .. "." .. tostring(signal)
if global.stations[name] ~= nil then
-- Search for valid destination station
found = false
for _, station in pairs(global.stations[name]) do
-- Check that the station exists
if station.valid then
local cb = station.get_control_behavior()
-- Check that the station in not disabled
if not cb or not cb.disabled then
found = true
break
end
end
end
if found then
local current = v.schedule.current
local records = v.schedule.records
-- Insert the destination station to the train schedule
table.insert(records, current + 1, {station=name, wait_conditions=records[current].wait_conditions })
v.train.schedule = {current=current + 1, records=records}
v.train.manual_mode = false
-- This train is not awaiting dispatch any more
global.awaiting_dispatch[i] = nil
-- If the train was sent to this dispatcher by another dispatcher, we need to update the train schedule and the dispatched variable
if global.dispatched[i] ~= nil then
reset_station(i)
end
-- Store the dispatched train
global.dispatched[i] = {train=v.train, station=name, current=v.train.schedule.current}
for _, player in pairs(game.players) do
player.create_local_flying_text({text="Train dispatched to "..name, position=v.station.position, speed=1, time_to_live=200})
end
debug("Train #", v.train.id, " has been dispatched to station '", name, "'")
else
--debug("Train #", v.train.id, " can't find any enabled station '", name, "'")
end
else
--debug("Train #", v.train.id, " can't find any station named '", name, "'")
end
end
end
end
end
script.on_event(defines.events.on_tick, tick)
-- Reset train schedule after a train has reached its destination
function reset_station(id)
-- If new train has no schedule, do not give it one and remove it from dispatching list.
if global.dispatched[id].train.schedule then
local records = global.dispatched[id].train.schedule.records
local current = global.dispatched[id].train.schedule.current
-- Only reset the train schedule if it has reached the correct station
if records[global.dispatched[id].current] ~= nil and records[global.dispatched[id].current].station == global.dispatched[id].station then
-- Remove destination station from schedule
table.remove(records, global.dispatched[id].current)
-- If the current station is after the destination station
if current > global.dispatched[id].current then
current = current - 1
end
-- Reset train schedule
global.dispatched[id].train.schedule = {current=current, records=records}
if has_locos(global.dispatched[id].train) then
global.dispatched[id].train.manual_mode = false
else
global.dispatched[id].train.manual_mode = true
end
end
end
global.dispatched[id] = nil
end
-- Check for any locomotives in the train
function has_locos(train)
if next(train.locomotives.front_movers) then
return true
end
if next(train.locomotives.back_movers) then
return true
end
return false
end
function any_to_string(...)
local text = ""
for _, v in ipairs{...} do
if type(v) == "table" then
text = text..serpent.block(v)
else
text = text..tostring(v)
end
end
return text
end
function print_game(...)
game.print(any_to_string(...))
end
-- Debug (print text to player console)
function debug(...)
if global.debug then
print_game(...)
end
end
-- Debug command
function cmd_debug(params)
local toggle = params.parameter
if not toggle then
if global.debug then
toggle = "disable"
else
toggle = "enable"
end
end
if toggle == "disable" then
global.debug = false
print_game("Debug mode disabled")
elseif toggle == "enable" then
global.debug = true
print_game("Debug mode enabled")
elseif toggle == "dump" then
for v, data in pairs(global) do
print_game(v, ": ", data)
end
elseif toggle == "dumplog" then
for v, data in pairs(global) do
log(any_to_string(v, ": ", data))
end
print_game("Dump written to log file")
end
end
commands.add_command("dispatcher-debug", {"command-help.dispatcher-debug"}, cmd_debug)