Skip to content

Commit dabb9dc

Browse files
committed
Train configuration UI, bring back support for train names, baic train status speaking support
1 parent 5f059ac commit dabb9dc

6 files changed

Lines changed: 214 additions & 0 deletions

File tree

locale/en/blueprints.cfg

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,14 @@ train-stop-limit=Train limit
113113
train-stop-limit-disabled=unlimited
114114
train-stop-limit-invalid=Invalid limit, must be non-negative integer
115115
train-stop-trains-incoming=Trains incoming
116+
117+
# Locomotive configuration
118+
locomotive-config-title=Locomotive Configuration
119+
locomotive-manual-mode=Manual mode
120+
locomotive-train-name=Train name
121+
locomotive-train-state=Train state
122+
locomotive-train-state-unknown=Train state unknown
123+
124+
# Train states
125+
train-state-manual=Train in manual
126+
train-state-headed-to=Train headed to __1__

locale/en/entity-info.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ ent-info-resource-infinite=at __1__ percent
6161
# We let the game localise the ghost prototype name, as well.
6262
ent-info-ghost=__1__ of __2__
6363

64+
# Rolling stock with train name
65+
ent-info-rolling-stock-of-train=__1__ of __2__
66+
6467
ent-info-self-character=You
6568

6669
ent-info-corpse-is-self=of your character

scripts/fa-info.lua

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ local RailDescriber = require("railutils.rail-describer")
5252
local RailAnnouncer = require("scripts.rails.announcer")
5353
local SurfaceHelper = require("scripts.rails.surface-helper")
5454
local RailQueries = require("railutils.queries")
55+
local TrainHelpers = require("scripts.rails.train-helpers")
5556

5657
local mod = {}
5758

@@ -1033,6 +1034,20 @@ local function ent_info_cargo_wagon(ctx)
10331034
end
10341035
end
10351036

1037+
---Handler for announcing train state on rolling stock (low priority, runs last)
1038+
---@param ctx fa.Info.EntInfoContext
1039+
local function ent_info_train_state(ctx)
1040+
local ent_type = ctx.ent.type
1041+
if
1042+
ent_type == "cargo-wagon"
1043+
or ent_type == "artillery-wagon"
1044+
or ent_type == "locomotive"
1045+
or ent_type == "fluid-wagon"
1046+
then
1047+
TrainHelpers.push_state_message(ctx.message, ctx.ent)
1048+
end
1049+
end
1050+
10361051
---@param ctx fa.Info.EntInfoContext
10371052
local function ent_info_fluid_connections(ctx)
10381053
local cursor_center = { x = ctx.cursor_pos.x + 0.5, y = ctx.cursor_pos.y + 0.5 }
@@ -1308,6 +1323,19 @@ function mod.ent_info(pindex, ent, is_scanner)
13081323
or ent.type == "curved-rail-b"
13091324
then
13101325
-- For rails, skip entity name - rail classification will be announced by ent_info_rail handler
1326+
elseif
1327+
ent.type == "cargo-wagon"
1328+
or ent.type == "artillery-wagon"
1329+
or ent.type == "locomotive"
1330+
or ent.type == "fluid-wagon"
1331+
then
1332+
-- For rolling stock, include train name
1333+
local train_name = TrainHelpers.get_name(ent)
1334+
ctx.message:fragment({
1335+
"fa.ent-info-rolling-stock-of-train",
1336+
Localising.get_localised_name_with_fallback(ent),
1337+
train_name,
1338+
})
13111339
else
13121340
ctx.message:fragment(Localising.get_localised_name_with_fallback(ent))
13131341
end
@@ -1484,6 +1512,9 @@ function mod.ent_info(pindex, ent, is_scanner)
14841512
run_handler(ent_info_combinator_connections)
14851513
run_handler(ent_info_circuit_network)
14861514

1515+
-- Train state handler (lowest priority, runs last)
1516+
run_handler(ent_info_train_state)
1517+
14871518
return ctx.message:build()
14881519
end
14891520

scripts/rails/train-helpers.lua

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
--[[
2+
Helper functions for managing trains and rolling stock.
3+
Provides utilities for naming trains, identifying locomotives, and describing train state.
4+
]]
5+
6+
local mod = {}
7+
8+
---Get the identifying locomotive for a train (the one with the lowest unit_number).
9+
---This provides a stable reference point for train-level operations.
10+
---@param rolling_stock LuaEntity A rolling stock entity (locomotive, wagon, etc)
11+
---@return LuaEntity? The locomotive with the lowest unit_number, or nil if no locomotives
12+
function mod.get_identifying_locomotive(rolling_stock)
13+
local train = rolling_stock.train
14+
if not train then return nil end
15+
16+
local locomotives = train.locomotives
17+
18+
-- No locomotive can be both a front and back mover, so check each list separately
19+
local identifying_loco = nil
20+
21+
-- Check front movers
22+
if locomotives.front_movers then
23+
for _, loco in ipairs(locomotives.front_movers) do
24+
if not identifying_loco or loco.unit_number < identifying_loco.unit_number then identifying_loco = loco end
25+
end
26+
end
27+
28+
-- Check back movers
29+
if locomotives.back_movers then
30+
for _, loco in ipairs(locomotives.back_movers) do
31+
if not identifying_loco or loco.unit_number < identifying_loco.unit_number then identifying_loco = loco end
32+
end
33+
end
34+
35+
return identifying_loco
36+
end
37+
38+
---Set the name of all locomotives in a train.
39+
---@param rolling_stock LuaEntity A rolling stock entity (locomotive, wagon, etc)
40+
---@param name string The name to set
41+
function mod.set_name(rolling_stock, name)
42+
local train = rolling_stock.train
43+
if not train then return end
44+
45+
local locomotives = train.locomotives
46+
47+
-- Set name on all front movers
48+
if locomotives.front_movers then
49+
for _, loco in ipairs(locomotives.front_movers) do
50+
loco.backer_name = name
51+
end
52+
end
53+
54+
-- Set name on all back movers
55+
if locomotives.back_movers then
56+
for _, loco in ipairs(locomotives.back_movers) do
57+
loco.backer_name = name
58+
end
59+
end
60+
end
61+
62+
---Get the name of a train.
63+
---@param rolling_stock LuaEntity A rolling stock entity (locomotive, wagon, etc)
64+
---@return string The train name (backer_name of identifying locomotive), or fallback
65+
function mod.get_name(rolling_stock)
66+
local identifying_loco = mod.get_identifying_locomotive(rolling_stock)
67+
if identifying_loco then return identifying_loco.backer_name end
68+
69+
-- No locomotive: fallback based on whether we have a train
70+
local train = rolling_stock.train
71+
if train then
72+
return "id " .. train.id
73+
else
74+
return "unnamed train"
75+
end
76+
end
77+
78+
---Add train state information to a message builder.
79+
---@param msgbuilder fa.MessageBuilder The message builder to add fragments to
80+
---@param rolling_stock LuaEntity A rolling stock entity (locomotive, wagon, etc)
81+
---@return boolean True if state was added, false if no state to report
82+
function mod.push_state_message(msgbuilder, rolling_stock)
83+
local train = rolling_stock.train
84+
if not train then return false end
85+
86+
-- Check if in manual mode
87+
if train.manual_mode then
88+
msgbuilder:fragment({ "fa.train-state-manual" })
89+
return true
90+
end
91+
92+
-- Check if headed to a station
93+
local path_end_stop = train.path_end_stop
94+
if path_end_stop then
95+
local station_name = path_end_stop.backer_name
96+
if station_name and station_name ~= "" then
97+
msgbuilder:fragment({ "fa.train-state-headed-to", station_name })
98+
return true
99+
end
100+
end
101+
102+
-- No verbalizable state
103+
return false
104+
end
105+
106+
return mod

scripts/ui/entity-ui.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ local equipment_grid_tab = require("scripts.ui.tabs.equipment-grid")
2626
local selector_combinator_tab = require("scripts.ui.tabs.selector-combinator")
2727
local arithmetic_combinator_tab = require("scripts.ui.tabs.arithmetic-combinator")
2828
local train_stop_tab = require("scripts.ui.tabs.train-stop")
29+
local locomotive_config_tab = require("scripts.ui.tabs.locomotive-config")
2930

3031
local mod = {}
3132

@@ -182,6 +183,9 @@ local function build_configuration_tabs(entity)
182183
-- Add train stop configuration
183184
if prototype.type == "train-stop" then table.insert(tabs, train_stop_tab.train_stop_tab) end
184185

186+
-- Add locomotive configuration
187+
if prototype.type == "locomotive" then table.insert(tabs, locomotive_config_tab.locomotive_config_tab) end
188+
185189
-- Future: Add other device-specific tabs here
186190
-- if prototype.type == "mining-drill" then ...
187191
-- if prototype.type == "lab" then ...
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--[[
2+
Locomotive configuration tab.
3+
4+
Provides a configuration form for locomotives with:
5+
- Manual/automatic mode toggle
6+
- Train name
7+
]]
8+
9+
local FormBuilder = require("scripts.ui.form-builder")
10+
local TrainHelpers = require("scripts.rails.train-helpers")
11+
local UiKeyGraph = require("scripts.ui.key-graph")
12+
13+
local mod = {}
14+
15+
---@class fa.ui.LocomotiveTabContext: fa.ui.TabContext
16+
---@field tablist_shared_state fa.ui.EntityUI.SharedState
17+
18+
---Render the locomotive configuration form
19+
---@param ctx fa.ui.LocomotiveTabContext
20+
---@return fa.ui.graph.Render?
21+
local function render_locomotive_config(ctx)
22+
local entity = ctx.tablist_shared_state.entity
23+
if not entity or not entity.valid then return nil end
24+
assert(entity.type == "locomotive", "render: entity is not a locomotive")
25+
26+
local train = entity.train
27+
if not train then return nil end
28+
29+
local builder = FormBuilder.FormBuilder.new()
30+
31+
-- Manual/automatic mode checkbox
32+
builder:add_checkbox("manual_mode", { "fa.locomotive-manual-mode" }, function()
33+
return train.manual_mode
34+
end, function(value)
35+
train.manual_mode = value
36+
end)
37+
38+
-- Train name field
39+
builder:add_textfield("name", {
40+
label = { "fa.locomotive-train-name" },
41+
get_value = function()
42+
return TrainHelpers.get_name(entity)
43+
end,
44+
set_value = function(value)
45+
TrainHelpers.set_name(entity, value)
46+
end,
47+
})
48+
49+
return builder:build()
50+
end
51+
52+
-- Create the tab descriptor
53+
mod.locomotive_config_tab = UiKeyGraph.declare_graph({
54+
name = "locomotive-config",
55+
title = { "fa.locomotive-config-title" },
56+
render_callback = render_locomotive_config,
57+
})
58+
59+
return mod

0 commit comments

Comments
 (0)