Hi all,
I'm at a bit of a loss with this one.
Is it possible to spawn a wagon and attach it to locomotive in response to research finishing?
The locomotive already exists in the scenario, and may already have wagons attached to it, and I would like to use research to add an extra wagon.
I think I need to use find_entities_filtered to get the train, and then maybe use cargo_wagons to add one to it? Maybe?
I was also thinking, if that won't work, perhaps there would be a way to wait until the train has reached a station, then spawn the wagon with the necessary coordinates? For example, once the event happens, I use go_to_station to make sure the train goes to the right one, and once it's there spawn the wagon.
I'm just not sure if this approach would stop the game from responding to other on_research_finished events until this one is complete.
I'm just not sure what the best way is.
If it helps, there are only two stations.
Sorry I can't add any example code for this one. I'm completely stumped on it.
Using on_research_finished to spawn a cargo wagon on a train
-
- Burner Inserter
- Posts: 18
- Joined: Tue Sep 26, 2023 11:45 am
- Contact:
Re: Using on_research_finished to spawn a cargo wagon on a train
You have to spawn the wagon and then use https://lua-api.factorio.com/latest/cla ... ling_stock.
But I don't know if creating wagons on the right spots on rails is doable.
But I don't know if creating wagons on the right spots on rails is doable.
The game does not wait for the train to reach the station.Doctor_Willis wrote: Sun Apr 21, 2024 8:19 am I'm just not sure if this approach would stop the game from responding to other on_research_finished events until this one is complete.
Re: Using on_research_finished to spawn a cargo wagon on a train
But you can schedule your action for a later time.
I didn't try this, so there is a chance it doesn't work quite as I imagine. But I'd go about it something like this:
- When the research is finished, locate the train.
- If you can read LuaTrain:station, (i.e. if it doesn't return nil), the train is stopped at a station, so you can get LuaTrain:back_rail and use it to place + connect the new wagon.
- If LuaTrain:station is nil, store LuaTrain::id in your global.table: e.g. global.wait_for_stop[train.id] = true. Add a handler for defines.events.on_train_changed_state.
- When the event triggers and you find event.train.id is stored in global.wait_for_stop, and if event.train.state == defines.train_state.wait_station, the train has arrived and you can then try to create/connect a wagon. (Don't forget to remove the ID from global.wait_for_stop or whatever you've named the table!)
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
-
- Burner Inserter
- Posts: 18
- Joined: Tue Sep 26, 2023 11:45 am
- Contact:
Re: Using on_research_finished to spawn a cargo wagon on a train
Thanks for the assistance. Both suggestions put me on the right track. I didn't end up needing a global table, but I got it to work:
It's probably a bit clunky in a few places, but it works for me so I'm happy
Code: Select all
local function createCargoWagon(new_carriage_coordinates)
local cargo_wagon = game.surfaces[1].create_entity{name = "cargo-wagon", position = new_carriage_coordinates, move_stuck_players = true, force = game.forces.neutral}
cargo_wagon.connect_rolling_stock(defines.rail_direction.front)
local new_trains = game.surfaces[1].find_entities_filtered({type = "locomotive"})
if #new_trains > 0 then
local new_train = new_trains[1].train
new_train.manual_mode = false
end
end
local function onTrainStateChanged(event, train, new_carriage_coordinates)
local changed_train = event.train
if changed_train == train and changed_train.state == defines.train_state.wait_station then
createCargoWagon(new_carriage_coordinates)
script.on_event(defines.events.on_train_changed_state, nil)
end
end
script.on_event(defines.events.on_research_finished, function(event)
local researchFinished = {
["train-carriage-2"] = function()
local trains = game.surfaces[1].find_entities_filtered({type = "locomotive"})
if #trains > 0 then
local train = trains[1].train
local loading_station = 1
local current_station = train.schedule.current
local new_carriage_coordinates = {-129.5,-83}
if current_station == loading_station and train.state == defines.train_state.wait_station then
createCargoWagon(new_carriage_coordinates)
elseif current_station == loading_station and train.state ~= defines.train_state.wait_station then
script.on_event(defines.events.on_train_changed_state, function(event)
onTrainStateChanged(event, train, new_carriage_coordinates)
end)
elseif current_station ~= loading_station then
train.go_to_station(1)
script.on_event(defines.events.on_train_changed_state, function(event)
onTrainStateChanged(event, train, new_carriage_coordinates)
end)
end
end
end
}
if researchFinished[event.research.name] then
researchFinished[event.research.name]()
end
end)
Re: Using on_research_finished to spawn a cargo wagon on a train
Congrats to your success! However, I think your work isn't finished yet. While it may work in single-player mode, you should expect desyncs in multiplayer games if a player joins after the event has run.Doctor_Willis wrote: Thu Apr 25, 2024 12:52 am Thanks for the assistance. Both suggestions put me on the right track. I didn't end up needing a global table, but I got it to work:
All players must have the same game state. Keep a list of the optional events in your global table; add the name of an event to the list when you enable the event, and remove it from the list when you disable it. Then, in script.on_load, go over the list and add the handler for each stored event. This way, joining players will listen to the same events as players who already were connected.
Code: Select all
script.on_event(defines.events.on_research_finished, function(event)
local researchFinished = {
["train-carriage-2"] = function()
local trains = game.surfaces[1].find_entities_filtered({type = "locomotive"})
if #trains > 0 then
local train = trains[1].train
local loading_station = 1
local current_station = train.schedule.current
local new_carriage_coordinates = {-129.5,-83}
if current_station == loading_station and train.state == defines.train_state.wait_station then
createCargoWagon(new_carriage_coordinates)
elseif current_station == loading_station and train.state ~= defines.train_state.wait_station then
script.on_event(defines.events.on_train_changed_state, function(event)
onTrainStateChanged(event, train, new_carriage_coordinates)
end)
elseif current_station ~= loading_station then
train.go_to_station(1)
script.on_event(defines.events.on_train_changed_state, function(event)
onTrainStateChanged(event, train, new_carriage_coordinates)
end)
end
end
end
}
if researchFinished[event.research.name] then
researchFinished[event.research.name]()
end
end)
In (most?) vanilla scenarios, all players will belong to game.forces["player"]. But in modded or in multiplayer games, there may be several forces, and each force has its own set of researched technologies. You should make sure that you update the train of the force that finished the research.
You always set new_carriage_coordinates to the same hard-coded position. That may work in your specific setup, where you've made sure that there are rails at {-129.5,-83}, but what about players who have built there base differently? What happens if the rails at that position are destroyed?
Code: Select all
local trains = game.surfaces[1].find_entities_filtered({type = "locomotive"})
if #trains > 0 then
local train = trains[1].train
…
end
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!
-
- Burner Inserter
- Posts: 18
- Joined: Tue Sep 26, 2023 11:45 am
- Contact:
Re: Using on_research_finished to spawn a cargo wagon on a train
Thank you for the input!
I'm still new to modding and still finding my feet.
I wasn't aware of the multiplayer issue. Would you mind going over it with a more concrete example/how to? I don't fully understand it.
And thanks for the heads up about the force. I'll make sure to change that to "player".
As for the other issues, they won't be a problem. This mod is never going public. I'm making it for a local tech-school, and using a very specific scenario where there will only be one train with the rails and train stops in specific coordinates.
And yes, there are additional research levels, and I'm currently in the process of factoring out the repeated code into functions, so it will be able to adapt to new research.
I'm still new to modding and still finding my feet.
I wasn't aware of the multiplayer issue. Would you mind going over it with a more concrete example/how to? I don't fully understand it.
And thanks for the heads up about the force. I'll make sure to change that to "player".
As for the other issues, they won't be a problem. This mod is never going public. I'm making it for a local tech-school, and using a very specific scenario where there will only be one train with the rails and train stops in specific coordinates.
And yes, there are additional research levels, and I'm currently in the process of factoring out the repeated code into functions, so it will be able to adapt to new research.
Re: Using on_research_finished to spawn a cargo wagon on a train
An example from my latest mod:Doctor_Willis wrote: Thu Apr 25, 2024 8:00 am Thank you for the input!
I'm still new to modding and still finding my feet.
I wasn't aware of the multiplayer issue. Would you mind going over it with a more concrete example/how to? I don't fully understand it.
Code: Select all
-- Map event names to event handlers
local event_handlers = {}
-- Map event names to event filters
local event_filters = {}
LCOE.optional_events = {
chunks = {
on_chunk_charted = LCOE_surfaces.on_chunk_charted,
},
chart_tags = {
on_chart_tag_modified = LCOE_charttags.on_chart_tag_changed,
on_chart_tag_removed = LCOE_charttags.on_chart_tag_changed,
},
portals = {
on_entity_damaged = LCOE_surfaces.on_entity_damaged,
on_entity_destroyed = LCOE_portals.restore_exit_portal,
on_gui_opened = LCOE_player.player_used_portal
},
}
-- Add filters for optional events
event_filters.on_entity_damaged = {
{filter = "damage-type", type = "explosion"},
}
event_handlers.on_player_joined_game = LCOE_player.on_player_joined_game
event_handlers.on_pre_player_left_game = LCOE_player.on_pre_player_left_game
event_handlers.on_pre_player_removed = LCOE_player.remove_player
event_handlers.on_player_died = LCOE_player.on_player_died
event_handlers.on_post_entity_died = LCOE_player.on_post_entity_died
event_filters.on_post_entity_died = {
{filter = "type", type = "character"}
}
event_handlers.on_character_corpse_expired = LCOE_corpse.remove_corpse_data
event_handlers.on_pre_player_mined_item = LCOE_corpse.remove_corpse_data
event_filters.on_pre_player_mined_item = {
{filter = "type", type = "character-corpse"}
}
…
------------------------------------------------------------------------------------
-- REGISTER EVENT HANDLERS! --
------------------------------------------------------------------------------------
LCOE_events.attach_events = function(event_list)
LCOE.assert(event_list, {"table", "nil"},
"table of event names and handlers or nil")
local id, filters
for event_name, event_handler in pairs(event_list or event_handlers) do
id = defines.events[event_name]
-- Register event
script.on_event(id, function(event)
event_handler(event)
end)
-- Set event filters?
filters = event_filters[event_name]
if filters and next(filters) then
script.set_event_filter(id, filters)
end
end
end
------------------------------------------------------------------------------------
-- REGISTER OPTIONAL EVENT HANDLERS! --
------------------------------------------------------------------------------------
LCOE_events.register_optional_events = function(group_name, event_name)
LCOE.assert(group_name, {"string", "nil"}, "name of table in LCOE.optional_events or nil")
LCOE.assert(event_name, {"string", "nil"}, "event_name or nil")
local event_list = LCOE.optional_events[group_name]
-- Attach single event
if event_name then
if event_list and event_list[event_name] then
LCOE_events.attach_events({[event_name] = event_list[event_name]})
global.optional_events[event_name] = group_name
end
-- Attach group of events
elseif event_list then
LCOE_events.register_optional_events(group)
for e_name, e_handler in pairs(event_list) do
global.optional_events[e_name] = group_name
end
-- Re-attach all events that are currently stored in global.optional_events
else
for e_name, group in pairs(global.optional_events) do
LCOE_events.register_optional_events(group, e_name)
end
end
end
------------------------------------------------------------------------------------
-- UNREGISTER EVENT HANDLERS! --
------------------------------------------------------------------------------------
LCOE_events.detach_events = function(event_list, keep_events)
LCOE.assert(event_list, {"table", "nil"},
"table of event names/handlers or nil")
LCOE.assert(keep_events, {"table", "nil"},
"table of event names to keep attached, or nil")
local keep_names = {}
for n, name in pairs(keep_events or {}) do
keep_names[name] = true
end
for event_name, event_handler in pairs(event_list or event_handlers) do
if keep_names[event_name] then
-- Ignore event
elseif script.get_event_handler(event_names[event_name]) then
-- Unregister event
script.on_event(event_names[event_name], nil)
-- Remove optional event from global.optional_events?
if event_list then
global.optional_events[event_name] = nil
end
end
end
end
-- Mod is running for the first time in this game, or
-- mods have been added/updated/removed
local function init()
…
LCOE_events.attach_events()
LCOE_events.register_optional_events()
…
end
script.on_init(init)
script.on_configuration_changed(init)
-- Saved game is loaded, we have read access to table "global", but
-- can't write to it!
script.on_load(function()
…
-- Attach events that we always need
LCOE_events.attach_events()
-- Attach all optional events that are stored in global.optional_events
LCOE_events.register_optional_events()
…
end)
-- Example: When a chart_tag has been added or removed by the mod,
-- this function is called to register/unregister handlers for the optional
-- events defined in LCOE.optional_events["chart_tags"].
LCOE_charttags.check_optional_events = function()
local need_event = false
-- We need events if at least one chart_tag has been stored for at least
-- one force
for f_name, f_data in pairs(global.force_data) do
for t_name, t_data in pairs(f_data.chart_tags or {}) do
if t_data.tag and t_data.tag.valid then
need_event = true
break
end
end
end
if need_event then
LCOE_events.register_optional_events("chart_tags")
else
LCOE_events.detach_events(LCOE.optional_events["chart_tags"])
end
end
OK, you'll probably get along with fixed values for force and surface in this case! I'm still doubtful about the position, though. Even if the position of the train stop will never change, your train will get longer, so after you've added a wagon, that position will be blocked. Shouldn't you try to determine the position for the new wagon based on the actual length of the train?And thanks for the heads up about the force. I'll make sure to change that to "player".
As for the other issues, they won't be a problem. This mod is never going public. I'm making it for a local tech-school, and using a very specific scenario where there will only be one train with the rails and train stops in specific coordinates.
Yes, I guess one function that is called with different arguments (e.g. the tech-level) would be enough …And yes, there are additional research levels, and I'm currently in the process of factoring out the repeated code into functions, so it will be able to adapt to new research.
A good mod deserves a good changelog. Here's a tutorial (WIP) about Factorio's way too strict changelog syntax!