--- Trains module --

When this module is loaded, it registers new -- events in order to track trains as locomotives and -- carriages are moved around. For this reason, you should use -- this library's Event module

-- @module Trains -- Event registration is performed at the bottom of this file, -- once all other functions have been defined require 'stdlib/event/event' require 'stdlib/surface' require 'stdlib/table' Trains = { _registry = {} } --- This event is fired when a train's id has changed. --

Train id's are dervied from a property of the train's main locomotive. -- That means when locomotives are attached/detached from carriages or other -- locomotives, the locomotive considered the main one can change, leading to a changed -- in train id.

--

For example: a train with a front and back locomotive will get its id -- from the front locomotive. If that is disconnected, the back locomotive will become -- the main one and the train's id will change

--

-- Event parameters
-- A table with the following properties: --

--

-- @usage ----Event.register(Trains.on_train_id_changed, my_handler) Trains.on_train_id_changed = script.generate_event_name() -- Finds the distinct trains from an array of locomotive entities -- @tparam LuaEntity[] locomotives -- @return List of train details tables for the distinct trains local function find_distinct_trains(locomotives) local train_data = {} for _, engine in pairs(locomotives) do local t = engine.train local id = Trains.get_train_id(t) local equals_id = function(x) return x.id == id end local existing = table.filter(train_data, equals_id) if #existing == 0 then table.insert(train_data, { train = t, id = id }) end end return train_data end --- Given search criteria (a table that contains at least a surface_name) -- searches the given surface for trains that match the criteria -- @usage ----Trains.find_filtered({ surface_name = "nauvis", state = defines.train_state.wait_station }) -- @tparam Table criteria Table with any keys supported by the Surface module.

--

If the name key isn't supplied, this will default to 'diesel-locomotive'

--

If the surface key isn't supplied, this will default to 1

-- @return A list of train details tables, if any are found matching the criteria. Otherwise the empty list.
train (LuaTrain)The LuaTrain instance
id (int)The id of the train
function Trains.find_filtered(criteria) criteria = criteria or {} -- Ensuure surface is set criteria.surface = criteria.surface or 'nauvis' -- Make sure 'locomotive' is specified as the type by default criteria.type = criteria.type or 'locomotive' -- Get locomotives by filter local locomotives = Surface.find_all_entities(criteria) -- Distinguish trains local train_data = find_distinct_trains(locomotives) --- Apply state filters if criteria.state then train_data = table.filter(train_data, function(data) return data.train.state == criteria.state end) end return train_data end --- Find the id of a LuaTrain instance -- @tparam LuaTrain train -- @treturn int function Trains.get_train_id(train) local loco = Trains.get_main_locomotive(train) return loco and loco.unit_number end --- Event fired when some change has happened to a locomotive -- @return void function Trains._on_locomotive_changed() -- For all the known trains local renames = {} for id, train in pairs(Trains._registry) do -- Check if their known ID is the same as the LuaTrain's dervied id local derived_id = Trains.get_train_id(train) -- If it's not if (id ~= derived_id) then -- Capture the rename table.insert(renames, {old_id = id , new_id = derived_id, train = train }) end end -- Go over the captured renaming operations for _, renaming in pairs(renames) do -- Rename it in the registry -- and dispatch a renamed event Trains._registry[renaming.new_id] = renaming.train table.remove_keys(Trains._registry, {renaming.old_id}) local event_data = { old_id = renaming.old_id, new_id = renaming.new_id, name = Trains.on_train_id_changed } Event.dispatch(event_data) end end --- Event fired when a new locomotive has been created -- @tparam LuaEntity new_locomotive The new entity -- @return void function Trains._on_locomotive_created(new_locomotive) local train_id = Trains.get_train_id(new_locomotive.train) if (Trains._registry[train_id] == nil) then Trains._registry[train_id] = new_locomotive.train end end --- Determines which locomotive in a train is the main one -- @tparam LuaTrain train -- @treturn LuaEntity the main locomotive function Trains.get_main_locomotive(train) if train.valid and train.locomotives and (#train.locomotives.front_movers > 0 or #train.locomotives.back_movers > 0) then return train.locomotives.front_movers and train.locomotives.front_movers[1] or train.locomotives.back_movers[1] end end --- Creates an Entity module-compatible entity from a train -- @tparam LuaTrain train -- @treturn table function Trains.to_entity(train) local name = "train-" .. Trains.get_train_id(train) return { name = name, valid = train.valid, equals = function(entity) return name == entity.name end } end --- Set user data on a train --

This is a helper method around Entity.set_data

-- @tparam LuaTrain train -- @tparam mixed data -- @return mixed function Trains.set_data(train, data) return Entity.set_data(Trains.to_entity(train), data) end --- Get user data on a train --

This is a helper method around Entity.get_data

-- @tparam LuaTrain train -- @return mixed function Trains.get_data(train) return Entity.get_data(Trains.to_entity(train)) end -- Creates a registry of known trains -- @return table A mapping of train id to LuaTrain object local function create_train_registry() local registry = {} local all_trains = Trains.find_filtered({surface_name = 1}) for _, trainInfo in pairs(all_trains) do registry[tonumber(trainInfo.id)] = trainInfo.train end return registry end -- When developers load this module, we need to -- attach some new events -- Filters events related to entity_type -- @tparam string event_parameter The event parameter to look inside to find the entity type -- @tparam string entity_type The entity type to filter events for -- @tparam callable callback The callback to invoke if the filter passes. The object defined in the event parameter is passed. local function filter_event(event_parameter, entity_type, callback) return function(evt) if(evt[event_parameter].name == entity_type) then callback(evt[event_parameter]) end end end -- When a locomotive is removed .. Event.register(defines.events.on_entity_died, filter_event('entity', 'diesel-locomotive', Trains._on_locomotive_changed)) Event.register(defines.events.on_picked_up_item, filter_event('item_stack', 'diesel-locomotive', Trains._on_locomotive_changed)) Event.register(defines.events.on_player_mined_item, filter_event('item_stack', 'diesel-locomotive', Trains._on_locomotive_changed)) Event.register(defines.events.on_robot_mined, filter_event('item_stack', 'diesel-locomotive', Trains._on_locomotive_changed)) -- When a locomotive is added .. Event.register(defines.events.on_built_entity, filter_event('created_entity', 'diesel-locomotive', Trains._on_locomotive_created)) Event.register(defines.events.on_robot_built_entity, filter_event('created_entity', 'diesel-locomotive', Trains._on_locomotive_created)) -- When the mod is initialized the first time Event.register(Event.core_events.init, function() Trains._registry = create_train_registry() end) return Trains