Skip to content

Commit 1e72a04

Browse files
Add remote interfaces and corresponding event data to support cross surface deliveries (#308)
- deliveries are created cross surface if connection is registered 3rd party mods must handle train transition and schedule updates themselves - new API: connect_surfaces(entity1 :: LuaEntity, entity2 :: LuaEntity, network_id :: int32) disconnect_surfaces(entity1 :: LuaEntity, entity2 :: LuaEntity) clear_all_surface_connections() reassign_delivery(old_train_id :: uint, new_train :: LuaTrain) :: bool get_or_create_next_temp_stop(train :: LuaTrain, schedule_index :: uint?) :: uint get_next_logistic_stop(train :: LuaTrain, schedule_index :: uint?) :: uint?, uint?, string?
1 parent 317e5a7 commit 1e72a04

13 files changed

+516
-149
lines changed

changelog.txt

+14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
11
---------------------------------------------------------------------------------------------------
2+
Version: 1.18.0
3+
Date: 2022-10-27
4+
Features:
5+
- Create deliveries across surfaces, train surface transitions have to be handled by 3rd party mods
6+
- added remote call connect_surfaces
7+
- added remote call disconnect_surfaces
8+
- added remote call clear_all_surface_connections
9+
- added remote call reassign_delivery
10+
- added remote call get_or_create_next_temp_stop
11+
- added remote call get_next_logistic_stop
12+
Changes:
13+
- added on_dispatcher_updated.new_deliveries as replacement for on_delivery_created
14+
- removed event on_delivery_created, running in same tick as dispatcher produced microstutter
15+
---------------------------------------------------------------------------------------------------
216
Version: 1.17.1
317
Date: 2022-10-10
418
Features:

control.lua

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ require "script.alert"
1515
require "script.utils" -- requires settings
1616
require "script.hotkey-events" -- requires print
1717

18-
require "script.interface"
1918
require "script.stop-update"
2019
require "script.dispatcher"
2120
require "script.stop-events"
2221
require "script.train-events"
23-
require "script.init" -- requires other modules loaded first
22+
require "script.train-interface" -- requires train-events
23+
require "script.surface-interface" -- requires stop-events
24+
require "script.interface" -- ties into other modules
25+
require "script.init" -- requires other modules loaded first

info.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "LogisticTrainNetwork",
3-
"version": "1.17.1",
3+
"version": "1.17.52",
44
"title": "LTN - Logistic Train Network",
55
"author": "Optera",
66
"contact": "https://forums.factorio.com/memberlist.php?mode=viewprofile&u=21729",

locale/de/base.cfg

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ error-stop-output-truncated=[LTN] Fehler: Inventar von Zug __1__ in Haltestelle
6060
error-invalid-delivery=[LTN] Fehler: Entferne ungültige Lieferung von Haltestelle __1__
6161
error-invalid-stop-index=[LTN] Fehler: Haltestelle mit ungültiger unit_number __1__ in global.LogisticTrainStops.
6262
error-invalid-request-index=[LTN] Fehler: Ungültiger Index __1__ in global.Dispatcher.Requests.
63+
6364
warning-dispatcher-disabled=[LTN] Warnung: Disponent deaktiviert. Es werden keine Lieferungen erstellt.
6465

6566
empty-depot-item=[LTN] Kein Zug für den Transport von Stückgut in Depots gefunden. Verarbeitung von Stückgut übersprungen

locale/en/base.cfg

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ error-stop-output-truncated=[LTN] Error: Inventory of train __2__ at stop __1__
6060
error-invalid-delivery=[LTN] Error: Removing invalid delivery from stop __1__
6161
error-invalid-stop-index=[LTN] Error: Invalid stop unit_number __1__ in global.LogisticTrainStops.
6262
error-invalid-request-index=[LTN] Error: Invalid index __1__ in global.Dispatcher.Requests.
63+
6364
warning-dispatcher-disabled=[LTN] Warning: Dispatcher disabled. No deliveries will be created.
6465
6566
empty-depot-item=[LTN] No train to transport items found in depots. Skipping item processing.

script/dispatcher.lua

+74-37
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ function OnTick(event)
156156
update_interval = tick - global.tick_interval_start,
157157
provided_by_stop = global.Dispatcher.Provided_by_Stop,
158158
requests_by_stop = global.Dispatcher.Requests_by_Stop,
159+
new_deliveries = global.Dispatcher.new_Deliveries,
159160
deliveries = global.Dispatcher.Deliveries,
160161
available_trains = global.Dispatcher.availableTrains,
161162
})
@@ -171,6 +172,7 @@ function OnTick(event)
171172
global.Dispatcher.Requests = {}
172173
global.Dispatcher.Provided_by_Stop = {}
173174
global.Dispatcher.Requests_by_Stop = {}
175+
global.Dispatcher.new_Deliveries = {}
174176
end
175177
end
176178

@@ -276,7 +278,42 @@ end
276278

277279
---- ProcessRequest ----
278280

279-
-- return a list ordered priority > #active_deliveries > item-count of {entity, network_id, priority, activeDeliveryCount, item, count, providing_threshold, providing_threshold_stacks, min_carriages, max_carriages, locked_slots}
281+
-- returns the string "number1|number2" in consistent order: the smaller number is always placed first
282+
local function sorted_pair(number1, number2)
283+
return (number1 < number2) and (number1..'|'..number2) or (number2..'|'..number1)
284+
end
285+
286+
-- return a list of matching { entity1, entity2, network_id } each connecting the two surfaces. The list will be empty if surface1 == surface2 and it will be nil if there are no matching connections. The second return value will be the number of entries in the list.
287+
local function find_surface_connections(surface1, surface2, force, network_id)
288+
if surface1 == surface2 then return {}, 0 end
289+
290+
local surface_pair_key = sorted_pair(surface1.index, surface2.index)
291+
local surface_connections = global.ConnectedSurfaces[surface_pair_key]
292+
if not surface_connections then return nil end
293+
294+
local matching_connections = {}
295+
local count=0
296+
for entity_pair_key, connection in pairs(surface_connections) do
297+
if connection.entity1.valid and connection.entity2.valid then
298+
if btest(network_id, connection.network_id)
299+
and connection.entity1.force == force and connection.entity2.force == force then
300+
count = count + 1
301+
matching_connections[count] = connection
302+
end
303+
else
304+
if debug_log then log("removing invalid surface connection "..entity_pair_key.." between surfaces "..surface_pair_key) end
305+
surface_connections[entity_pair_key] = nil
306+
end
307+
end
308+
309+
if count > 0 then
310+
return matching_connections, count
311+
else
312+
return nil, nil
313+
end
314+
end
315+
316+
-- return a list ordered priority > #active_deliveries > item-count of {entity, network_id, priority, activeDeliveryCount, item, count, providing_threshold, providing_threshold_stacks, min_carriages, max_carriages, locked_slots, surface_connections}
280317
local function getProviders(requestStation, item, req_count, min_length, max_length)
281318
local stations = {}
282319
local providers = global.Dispatcher.Provided[item]
@@ -294,28 +331,34 @@ local function getProviders(requestStation, item, req_count, min_length, max_len
294331
-- log("DEBUG: comparing 0x"..format("%x", band(requestStation.network_id)).." & 0x"..format("%x", band(stop.network_id)).." = 0x"..format("%x", band(matched_networks)) )
295332

296333
if stop.entity.force == force
297-
and stop.entity.surface == surface
298334
and matched_networks ~= 0
299335
-- and count >= stop.providing_threshold
300336
and (stop.min_carriages == 0 or max_length == 0 or stop.min_carriages <= max_length)
301-
and (stop.max_carriages == 0 or min_length == 0 or stop.max_carriages >= min_length) then --check if provider can actually service trains from requester
337+
and (stop.max_carriages == 0 or min_length == 0 or stop.max_carriages >= min_length) then
338+
--check if provider can accept more trains
302339
local activeDeliveryCount = #stop.active_deliveries
303-
local from_network_id_string = format("0x%x", band(stop.network_id))
304340
if activeDeliveryCount and (stop.max_trains == 0 or activeDeliveryCount < stop.max_trains) then
305-
if debug_log then log("found "..count.."("..tostring(stop.providing_threshold)..")".."/"..req_count.." ".. item.." at "..stop.entity.backer_name.." {"..from_network_id_string.."}, priority: "..stop.provider_priority..", active Deliveries: "..activeDeliveryCount.." min_carriages: "..stop.min_carriages..", max_carriages: "..stop.max_carriages..", locked Slots: "..stop.locked_slots) end
306-
stations[#stations +1] = {
307-
entity = stop.entity,
308-
network_id = matched_networks,
309-
priority = stop.provider_priority,
310-
activeDeliveryCount = activeDeliveryCount,
311-
item = item,
312-
count = count,
313-
providing_threshold = stop.providing_threshold,
314-
providing_threshold_stacks = stop.providing_threshold_stacks,
315-
min_carriages = stop.min_carriages,
316-
max_carriages = stop.max_carriages,
317-
locked_slots = stop.locked_slots,
318-
}
341+
-- check if surface transition is possible
342+
local surface_connections, surface_connections_count = find_surface_connections(surface, stop.entity.surface, force, matched_networks)
343+
if surface_connections then -- for same surfaces surface_connections = {}
344+
local from_network_id_string = format("0x%x", band(stop.network_id))
345+
if debug_log then log("found "..count.."("..tostring(stop.providing_threshold)..")".."/"..req_count.." ".. item.." at "..stop.entity.backer_name.." {"..from_network_id_string.."}, priority: "..stop.provider_priority..", active Deliveries: "..activeDeliveryCount..", min_carriages: "..stop.min_carriages..", max_carriages: "..stop.max_carriages..", locked Slots: "..stop.locked_slots..", #surface_connections: "..(surface_connections_count)) end
346+
stations[#stations +1] = {
347+
entity = stop.entity,
348+
network_id = matched_networks,
349+
priority = stop.provider_priority,
350+
activeDeliveryCount = activeDeliveryCount,
351+
item = item,
352+
count = count,
353+
providing_threshold = stop.providing_threshold,
354+
providing_threshold_stacks = stop.providing_threshold_stacks,
355+
min_carriages = stop.min_carriages,
356+
max_carriages = stop.max_carriages,
357+
locked_slots = stop.locked_slots,
358+
surface_connections = surface_connections,
359+
surface_connections_count = surface_connections_count,
360+
}
361+
end
319362
end
320363
end
321364
end
@@ -324,12 +367,16 @@ local function getProviders(requestStation, item, req_count, min_length, max_len
324367
sort(stations, function(a, b)
325368
if a.priority ~= b.priority then --sort by priority, will result in train queues if trainlimit is not set
326369
return a.priority > b.priority
370+
elseif a.surface_connections_count ~= b.surface_connections_count then --sort providers without surface transition to top
371+
return min(a.surface_connections_count, 1) < min(b.surface_connections_count, 1)
327372
elseif a.activeDeliveryCount ~= b.activeDeliveryCount then --sort by #deliveries
328373
return a.activeDeliveryCount < b.activeDeliveryCount
329374
else
330375
return a.count > b.count --finally sort by item count
331376
end
332377
end)
378+
379+
if debug_log then log ("(getProviders) sorted providers: "..serpent.block(stations)) end
333380
return stations
334381
end
335382

@@ -511,11 +558,6 @@ function ProcessRequest(reqIndex, request)
511558
local matched_network_id_string = format("0x%x", band(providerData.network_id))
512559

513560
if message_level >= 3 then printmsg({"ltn-message.provider-found", from_gps, tostring(providerData.priority), tostring(providerData.activeDeliveryCount), providerData.count, "[" .. itype .. "=" .. iname .. "]"}, requestForce, true) end
514-
-- if debug_log then
515-
-- for n, provider in pairs (providers) do
516-
-- log("Provider["..n.."] "..provider.entity.backer_name..": Priority "..tostring(provider.priority)..", "..tostring(provider.activeDeliveryCount).." deliveries, "..tostring(provider.count).." "..item.." available.")
517-
-- end
518-
-- end
519561

520562
-- limit deliverySize to count at provider
521563
local deliverySize = count
@@ -628,14 +670,18 @@ function ProcessRequest(reqIndex, request)
628670
schedule.records[#schedule.records + 1] = NewScheduleRecord(depot.entity.backer_name, "inactivity", depot_inactivity)
629671

630672
-- make train go to specific stations by setting a temporary waypoint on the rail the station is connected to
631-
if from_rail and from_rail_direction then
673+
-- schedules cannot have temporary stops on a different surface, those need to be added when the delivery is updated with a train on a different surface
674+
if from_rail and from_rail_direction
675+
and depot.entity.surface == from_rail.surface then
632676
schedule.records[#schedule.records + 1] = NewTempScheduleRecord(from_rail, from_rail_direction)
633677
else
634678
if debug_log then log("(ProcessRequest) Warning: creating schedule without temporary stop for provider.") end
635679
end
636680
schedule.records[#schedule.records + 1] = NewScheduleRecord(from, "item_count", "", loadingList)
637681

638-
if to_rail and to_rail_direction then
682+
if to_rail and to_rail_direction
683+
and depot.entity.surface == to_rail.surface
684+
and (from_rail and to_rail.surface == from_rail.surface) then
639685
schedule.records[#schedule.records + 1] = NewTempScheduleRecord(to_rail, to_rail_direction)
640686
else
641687
if debug_log then log("(ProcessRequest) Warning: creating schedule without temporary stop for requester.") end
@@ -680,6 +726,7 @@ function ProcessRequest(reqIndex, request)
680726

681727
if debug_log then log(" "..loadingListItem..", "..loadingList[i].count.." in "..loadingList[i].stacks.." stacks ") end
682728
end
729+
global.Dispatcher.new_Deliveries[#global.Dispatcher.new_Deliveries+1] = selectedTrain.id
683730
global.Dispatcher.Deliveries[selectedTrain.id] = {
684731
force = requestForce,
685732
train = selectedTrain,
@@ -689,6 +736,7 @@ function ProcessRequest(reqIndex, request)
689736
to = to,
690737
to_id = toID,
691738
network_id = providerData.network_id,
739+
surface_connections = providerData.surface_connections,
692740
shipment = shipment}
693741
global.Dispatcher.availableTrains_total_capacity = global.Dispatcher.availableTrains_total_capacity - global.Dispatcher.availableTrains[selectedTrain.id].capacity
694742
global.Dispatcher.availableTrains_total_fluid_capacity = global.Dispatcher.availableTrains_total_fluid_capacity - global.Dispatcher.availableTrains[selectedTrain.id].fluid_capacity
@@ -712,18 +760,7 @@ function ProcessRequest(reqIndex, request)
712760
end
713761
end
714762

715-
script.raise_event(on_delivery_created_event, {
716-
train_id = selectedTrain.id,
717-
train = selectedTrain,
718-
from = from,
719-
from_id = fromID,
720-
to = to,
721-
to_id = toID,
722-
shipment = shipment
723-
})
724-
725-
-- return train ID = delivery ID
726-
return selectedTrain.id
763+
return selectedTrain.id -- deliveries are indexed by train.id
727764
end
728765

729766

script/init.lua

+5-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ local function initialize(oldVersion, newVersion)
3535
---- initialize stops
3636
global.LogisticTrainStops = global.LogisticTrainStops or {}
3737

38+
-- table of connections per surface used to decide if providers from another surface are valid sources
39+
-- { [surface1.index|surface2.index] = { [entity1.unit_number|entity2.unit_number] = { entity1, entity2, network_id } }
40+
-- entity_key_pairs are automatically removed during delivery processing if at least one of the referenced entities becomes invalid
41+
global.ConnectedSurfaces = global.ConnectedSurfaces or {}
42+
3843
-- clean obsolete global
3944
global.Dispatcher.Requested = nil
4045
global.Dispatcher.Orders = nil
@@ -257,7 +262,6 @@ local function registerEvents()
257262
script.on_event( defines.events.on_robot_built_entity, OnEntityCreated, filters_on_built )
258263
script.on_event( {defines.events.script_raised_built, defines.events.script_raised_revive, defines.events.on_entity_cloned}, OnEntityCreated )
259264

260-
261265
script.on_event( defines.events.on_pre_player_mined_item, OnEntityRemoved, filters_on_mined )
262266
script.on_event( defines.events.on_robot_pre_mined, OnEntityRemoved, filters_on_mined )
263267
script.on_event( defines.events.on_entity_died, function(event) OnEntityRemoved(event, true) end, filters_on_mined )

0 commit comments

Comments
 (0)