Page 1 of 1
Anyway to modify/disable an individual Entity's (specifically a RollingStockPrototype) Pictures at runtime?
Posted: Sun Sep 21, 2025 2:30 pm
by Zeritor
As it's on the Prototype, it doesn't look like you can modify it at runtime and even if you could, you would affect all instances.
I want to be able to show/hide Layers at the Entity level based on conditions. However, I can't see a way to do that via run time scripting. Is there anything I can do?
Re: Anyway to modify/disable an individual Entity's (specifically a RollingStockPrototype) Pictures at runtime?
Posted: Sun Sep 21, 2025 2:58 pm
by robot256
Your script can attach drawn sprites to a rolling stock entity and they will move with it. But it would be really hard to make it look like part of the wagon.
I wrote a library specifically to sneakily replay one tonight stock entity with another to change the graphics. You are welcome to depend on it if it's useful to you.
https://mods.factorio.com/mod/Robot256Lib
Re: Anyway to modify/disable an individual Entity's (specifically a RollingStockPrototype) Pictures at runtime?
Posted: Sun Sep 21, 2025 9:18 pm
by Osmo
You
can use LuaRendering to draw stuff in a way thay it looks like a part of the wagon.

- 5526_0000h46m57s_nauvis.jpg (961.57 KiB) Viewed 376 times
But it is kind of complex, and swapping the wagon to a different one may be better. Or just not modifying graphics at all if its not strictly necessary
Re: Anyway to modify/disable an individual Entity's (specifically a RollingStockPrototype) Pictures at runtime?
Posted: Sun Sep 21, 2025 9:43 pm
by Zeritor
I've just been messing with the `rendering` global. It doesn't quote work perfectly I find. I might be doing something wrong.
Compared to using the normal way of spriting a wagon, I'm using an animation and using the wagon's rotation to index the correct frame in the animation.
Only it drifts off centre on corners compared to the normal way, as well as the rotation not being quite right (seems to be using the angle between the front/rear wheels, when a wagon is actually floatier than that).
Any tips?
Re: Anyway to modify/disable an individual Entity's (specifically a RollingStockPrototype) Pictures at runtime?
Posted: Sun Sep 21, 2025 11:24 pm
by Osmo
Zeritor wrote: Sun Sep 21, 2025 9:43 pm
Only it drifts off centre on corners compared to the normal way, as well as the rotation not being quite right (seems to be using the angle between the front/rear wheels, when a wagon is actually floatier than that).
Any tips?
That's because due to the game's apparent camera angle, the actual rotation is not directly proportional to the rotation it should have in 3d.
Here's a function for that (project_2d_to_3d)
Code: Select all
local tau = 2 * math.pi -- ~6.2832
local tau_inv = 1 / tau -- ~0.1592
local sqrt2 = math.sqrt(2) -- ~1.4142
local sqrt2_inv = 1 / sqrt2 -- ~0.7071
function orientation_to_angle(orientation)
return (0.25 - orientation) * tau
end
function angle_to_orientation(angle)
return 0.25 - angle * tau_inv
end
local atan2, cos, sin = math.atan2, math.cos, math.sin
-- For a desired 2D orientation, return the 3D orientation factor necessary to visually match it.
function project_2d_to_3d(o)
local angle = orientation_to_angle(o)
angle = atan2(sin(angle)*sqrt2, cos(angle))
return mod(angle_to_orientation(angle), 1)
end
Re: Anyway to modify/disable an individual Entity's (specifically a RollingStockPrototype) Pictures at runtime?
Posted: Sun Sep 21, 2025 11:56 pm
by Zeritor
Thanks so much, that's made a big difference. The overlay texture is now rotated correctly.
I still have an offset issue, then a totally broken rotation if I reverse the wagon.

- 09-22-2025, 00-54-52.png (6.91 MiB) Viewed 281 times
Not sure if this is to do with how I'm defining the animation - or how I'm setting the angle. Currently I'm setting the animation target to be the wagon, then doing this in an `onTick`
Code: Select all
local orientation = locked_wagon.entity.orientation
local osmoFix = project_2d_to_3d(orientation)
local frameIndex = math.floor(osmoFix * 256)
log("Wagon Animation Update, Wagon Orientastion " .. locked_wagon.entity.orientation .. " Frame Chosen:" .. frameIndex)
locked_wagon.animation.animation_offset = frameIndex
Re: Anyway to modify/disable an individual Entity's (specifically a RollingStockPrototype) Pictures at runtime?
Posted: Mon Sep 22, 2025 12:05 am
by Zeritor
Fixed the wrong image by wrapping to keep the frame index in range. Just to tackle the position oddness now.
Re: Anyway to modify/disable an individual Entity's (specifically a RollingStockPrototype) Pictures at runtime?
Posted: Mon Sep 22, 2025 12:18 am
by Zeritor
Ahhhh, I just found there's `LuaEntity.draw_data.position`, setting the `target` of my animation to that now:

- 09-22-2025, 01-18-08.png (8.54 MiB) Viewed 250 times
Not perfect (and the renders are bad, just threw in random stuff I found on open source model sites to see if it was possible).
Thanks again - wouldn't have got here without that last push with your function.
Re: Anyway to modify/disable an individual Entity's (specifically a RollingStockPrototype) Pictures at runtime?
Posted: Mon Sep 22, 2025 12:25 am
by Zeritor
Though when the train is going fast they still look bad, hmm. It's like I'm setting the position for the last draw, so it's always lagging behind.

- 09-22-2025, 01-37-39.png (376.72 KiB) Viewed 223 times
Even the rotation is wrong, hmm. It's like I'm getting the data at the start of the frame, applying that, then the train's update is handled, moving it forwards on the rail. Is there no way to do a "on post tick" or similar to other game engines?
Re: Anyway to modify/disable an individual Entity's (specifically a RollingStockPrototype) Pictures at runtime?
Posted: Mon Sep 22, 2025 12:49 am
by Osmo
Zeritor wrote: Mon Sep 22, 2025 12:25 am
Though when the train is going fast they still look bad, hmm. It's like I'm setting the position for the last draw, so it's always lagging behind.
Even the rotation is wrong, hmm. It's like I'm getting the data at the start of the frame, applying that, then the train's update is handled, moving it forwards on the rail. Is there no way to do a "on post tick" or similar to other game engines?
No, some events are raised later in the tick, but even then train position may not have updated yet. But you can add a train's velocity to the position. Not sure how to handle change in rotation though, but it shouldn't be visible as much as position, especially since the faster it moves the harder it is to see.
Re: Anyway to modify/disable an individual Entity's (specifically a RollingStockPrototype) Pictures at runtime?
Posted: Mon Sep 22, 2025 12:55 am
by Zeritor
I can get the speed, as in a total magnitude but not the velocity, as in the direction. In fact I see very little for direction. I guess I can use orientation and speed with vector math to get x/y components.
Re: Anyway to modify/disable an individual Entity's (specifically a RollingStockPrototype) Pictures at runtime?
Posted: Mon Sep 22, 2025 1:17 am
by Zeritor
Yeah, Vector maths works.

- 09-22-2025, 02-10-07.png (1.1 MiB) Viewed 170 times
Position is fine now, rotation not so much. It doesn't look nearly as bad and only really notice it if you pause/screenshot a very fast train.
Can't see a way to predict rotation as it looks like there's no info for what's next in regards to rail.
Code is now:
Code: Select all
local tau = 2 * math.pi -- ~6.2832
local tau_inv = 1 / tau -- ~0.1592
local sqrt2 = math.sqrt(2) -- ~1.4142
function orientation_to_angle(orientation)
return (0.25 - orientation) * tau
end
function angle_to_orientation(angle)
return 0.25 - angle * tau_inv
end
local atan2, cos, sin = math.atan2, math.cos, math.sin
-- For a desired 2D orientation, return the 3D orientation factor necessary to visually match it.
function project_2d_to_3d(o)
local angle = orientation_to_angle(o)
angle = atan2(sin(angle)*sqrt2, cos(angle))
return math.fmod(angle_to_orientation(angle), 1)
end
-- Get converted angle of Wagon
local projectedOrientation = project_2d_to_3d(wagon.entity.draw_data.orientation)
local projectedOrientationAsAngle = orientation_to_angle(projectedOrientation)
-- Calculate frame index (based on 'animation' length)
local frameIndex = math.floor(projectedOrientation * 256)
-- Wrap frame index to always be within the 0-256 range
while(frameIndex < 0) do
frameIndex = 256 + frameIndex
end
while(frameIndex >= 256) do
frameIndex = frameIndex - 256
end
-- Set 'rotation' via the sprite sheet
wagon.animation.animation_offset = frameIndex
-- Grab current position/speed
local current_position = wagon.entity.draw_data.position
local speed = wagon.entity.speed
-- Calculate velocity vector from speed and orientation
local velocity_x = speed * math.cos(projectedOrientationAsAngle)
local velocity_y = speed * math.sin(projectedOrientationAsAngle)
-- Predict position
local predicted_position = {
x = current_position.x + velocity_x,
y = current_position.y - velocity_y
}
-- Draw at the predicted position
wagon.animation.target = predicted_position
Re: Anyway to modify/disable an individual Entity's (specifically a RollingStockPrototype) Pictures at runtime?
Posted: Mon Sep 22, 2025 2:07 am
by Zeritor
Right, I have it almost flawless apart from the very bad edge cases of extreme speed and tight angles. Even then you can't see it anymore without pausing/screenshotting.
I am bad at math and don't know how to apply it, so I have just done what I can and bodged in magic constants everywhere (that will probably vary by wagon size, texture size, etc). I had a few weird issues I couldn't explain so throwing in random rotation checks (always seemed to be when going from 0 -> 1 and wrapping).

- 09-22-2025, 03-04-44.png (1.14 MiB) Viewed 119 times
Up close you can see it's not perfect.
But in a video, it looks pretty good:
https://streamable.com/a1zdqp
Code:
Code: Select all
local tau = 2 * math.pi -- ~6.2832
local tau_inv = 1 / tau -- ~0.1592
local sqrt2 = math.sqrt(2) -- ~1.4142
local sqrt2_inv = 1 / sqrt2 -- ~0.7071
function orientation_to_angle(orientation)
return (0.25 - orientation) * tau
end
function angle_to_orientation(angle)
return 0.25 - angle * tau_inv
end
local atan2, cos, sin = math.atan2, math.cos, math.sin
-- For a desired 2D orientation, return the 3D orientation factor necessary to visually match it.
function project_2d_to_3d(o)
local angle = orientation_to_angle(o)
angle = atan2(sin(angle)*sqrt2, cos(angle))
return math.fmod(angle_to_orientation(angle), 1)
end
local check_locked_wagons = function(tick)
-- Initialize rotation history storage if it doesn't exist
if not storage.ftd.rotation_history then
storage.ftd.rotation_history = {}
end
for _, locked_wagon in pairs(storage.ftd.locked_wagons) do
if locked_wagon.entity and locked_wagon.entity.valid then
-- Initialize rotation history for this wagon if it doesn't exist
if not locked_wagon.cumulative_rotation then
locked_wagon.cumulative_rotation = 0
locked_wagon.last_orientation = locked_wagon.entity.draw_data.orientation
end
local current_angle = locked_wagon.entity.draw_data.orientation
-- Calculate rotation change since last update
local rotation_change = 0
if locked_wagon.last_orientation then
rotation_change = current_angle - locked_wagon.last_orientation
-- Handle angle wrapping (crossing 0/1 boundary)
if rotation_change > 0.5 then
rotation_change = rotation_change - 1
elseif rotation_change < -0.5 then
rotation_change = rotation_change + 1
end
end
-- Update cumulative rotation with decay and clamp to reasonable range
locked_wagon.cumulative_rotation = locked_wagon.cumulative_rotation * 0.15 + rotation_change
-- Update last orientation for next frame
locked_wagon.last_orientation = current_angle
-- Debug logging
log("Current: " .. current_angle .. ", Cumulative: " .. locked_wagon.cumulative_rotation .. ", Change: " .. rotation_change)
-- Get converted angle of Wagon using current orientation + prediction
local predicted_orientation = current_angle + locked_wagon.cumulative_rotation
-- Ensure predicted orientation is within valid range
while predicted_orientation < 0 do
predicted_orientation = predicted_orientation + 1
end
while predicted_orientation >= 1 do
predicted_orientation = predicted_orientation - 1
end
local projectedOrientation = project_2d_to_3d(predicted_orientation)
local projectedOrientationAsAngle = orientation_to_angle(projectedOrientation)
-- Calculate frame index (based on 'animation' length)
local frameIndex = math.floor(projectedOrientation * 256)
-- Wrap frame index to always be within the 0-256 range
while(frameIndex < 0) do
frameIndex = 256 + frameIndex
end
while(frameIndex >= 256) do
frameIndex = frameIndex - 256
end
-- Set 'rotation' via the sprite sheet
locked_wagon.animation.animation_offset = frameIndex
-- Grab current position/speed
local current_position = locked_wagon.entity.draw_data.position
local speed = locked_wagon.entity.speed
-- Calculate velocity vector from speed and orientation
local velocity_x = speed * math.cos(orientation_to_angle(project_2d_to_3d(current_angle)))
local velocity_y = speed * math.sin(orientation_to_angle(project_2d_to_3d(current_angle)))
-- Predict position
local predicted_position = {
x = current_position.x + (velocity_x * 1.1),
y = current_position.y - (velocity_y * 0.85)
}
-- Draw at the predicted position
locked_wagon.animation.target = predicted_position
end
end
end
return check_locked_wagons
I still need to tidy it as I very rapidly threw this together, but pretty happy now.