Hola a todo el mundo,
Después de la aparición de 
RailWorks y de la última actualización (
v97.6a), he empezado a investigar las nuevas posibilidades.
1.-Activación de efectos de vapor, según los valores de ciertos controles. Por ejemplo, el vapor del silbato

que sólo se emite cuando lo hacemos sonar. Esto es sencillo y tan sólo requiere el siguiente código dentro de la función 
OnControlValueChange ( name, index, value ) del archivo 
lua Engine Script, el que está en la propia carpeta de la máquina (
Engine)
- Código: Seleccionar todo
-       if name == "Horn" then
 
 if value > 0 then
 Call( "Vapor_Sirena:SetEmitterActive", 1 );
 else
 Call( "Vapor_Sirena:SetEmitterActive", 0 );
 end
 
 end
 
Previamente, dentro de la función 
Initialise () de ese mismo archivo, hay que hacer que ese 
emitter esté inactivo al empezar a jugar. Eso se consigue con la instrucción
- Código: Seleccionar todo
-    Call( "Vapor_Sirena:SetEmitterActive", 0 );
Un buen efecto es que, con el regulador abierto, se escape algo de vapor por los prensaestopas de los cilindros:

La inicialización en  la función 
Initialise ()  del archivo 
lua Engine Script para un 
emitter en cada cilindro es
- Código: Seleccionar todo
-    Call( "Vapor_cilindre_dreta:SetEmitterActive", 0 );
 Call( "Vapor_cilindre_esquerra:SetEmitterActive", 0 );
 
y en la función 
OnControlValueChange ( name, index, value ) de ese mismo archivo el código es
- Código: Seleccionar todo
-       if name == "Regulator" then
 
 if value > 0.05 then
 
 Call( "Vapor_cilindre_dreta:SetEmitterActive", 1 );
 Call( "Vapor_cilindre_esquerra:SetEmitterActive", 1 );
 .......................
 .......................
 
El caso de las purgas es algo más complejo, porque se depende de los valores de 
dos controles, a saber, el mando de las purgas (
CylinderCock) y el regulador (
Regulator). En efecto, con las purgas abiertas, si se cierra el regulador, el vapor debe dejar de salir y, con el regulador cerrado, al abrir las purgas no debe salir vapor. Contando con cuatro emisores de vapor, el código a incluir en la función 
OnControlValueChange ( name, index, value ) del archivo 
lua Engine Script es éste:
- Código: Seleccionar todo
-       if name == "CylinderCock" then
 
 if value > 0 then
 
 if Call( "*:ControlExists", "Regulator", 0 ) ~= zero then
 rgValue = Call( "*:GetControlValue", "Regulator", 0 );
 if rgValue > 0 then
 Call( "Vapor_purga_dreta1:SetEmitterActive", 1 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 1 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 1 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 1 );
 else
 Call( "Vapor_purga_dreta1:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 0 );
 end
 else
 Call( "Vapor_purga_dreta1:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 0 );
 end
 else
 Call( "Vapor_purga_dreta1:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 0 );
 end
 
 end
 
 if name == "Regulator" then
 
 if value > 0.05 then
 
 Call( "Vapor_cilindre_dreta:SetEmitterActive", 1 );
 Call( "Vapor_cilindre_esquerra:SetEmitterActive", 1 );
 
 if Call( "*:ControlExists", "CylinderCock", 0 ) ~= zero then
 purgValue = Call( "*:GetControlValue", "CylinderCock", 0 );
 if purgValue > 0 then
 Call( "Vapor_purga_dreta1:SetEmitterActive", 1 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 1 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 1 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 1 );
 else
 Call( "Vapor_purga_dreta1:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 0 );
 end
 end
 
 else
 Call( "Vapor_cilindre_dreta:SetEmitterActive", 0 );
 Call( "Vapor_cilindre_esquerra:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta1:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 0 );
 end
 
 end
 
 
y, como antes, en la función 
Initialise () de ese mismo archivo, hay que hacer que esos 
emitters empiecen inactivos con el código
- Código: Seleccionar todo
-    Call( "Vapor_purga_dreta1:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 0 );
 
El efecto es éste:

Todo el archivo 
lua Engine Script queda así:
- Código: Seleccionar todo
- --------------------------------------------------------------------------------------
 -- KUJU / Rail Simulator
 --------------------------------------------------------------------------------------
 -- INITIALISE
 
 function Initialise ()
 
 -- All emitters are active by default so turn off some of them
 --Call( "Fum_xemeneia:SetEmitterActive", 0 );
 Call( "Vapor_Sirena:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta1:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 0 );
 Call( "Vapor_cilindre_dreta:SetEmitterActive", 0 );
 Call( "Vapor_cilindre_esquerra:SetEmitterActive", 0 );
 
 --   call("BeginUpdate");
 
 end
 
 ------------------------------------------------------------
 -- OnControlValueChange
 ------------------------------------------------------------
 -- Called when a cab control is modified
 ------------------------------------------------------------
 -- Parameters:
 --   name   = Name of the control
 --   index   = Index of the control
 --   value   = Modified control value
 ------------------------------------------------------------
 
 function OnControlValueChange ( name, index, value )
 
 local rgValue = 0;
 local purgValue = 0;
 local increment = 0;
 local zero = 0
 
 if Call( "*:ControlExists", name, index ) then
 
 Call( "*:SetControlValue", name, index, value );
 
 end
 
 
 if name == "Horn" then
 
 if value > 0 then
 Call( "Vapor_Sirena:SetEmitterActive", 1 );
 else
 Call( "Vapor_Sirena:SetEmitterActive", 0 );
 end
 
 end
 
 
 if name == "CylinderCock" then
 
 if value > 0 then
 
 if Call( "*:ControlExists", "Regulator", 0 ) ~= zero then
 rgValue = Call( "*:GetControlValue", "Regulator", 0 );
 if rgValue > 0 then
 Call( "Vapor_purga_dreta1:SetEmitterActive", 1 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 1 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 1 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 1 );
 else
 Call( "Vapor_purga_dreta1:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 0 );
 end
 else
 Call( "Vapor_purga_dreta1:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 0 );
 end
 else
 Call( "Vapor_purga_dreta1:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 0 );
 end
 
 end
 
 
 if name == "Regulator" then
 
 if value > 0.05 then
 
 Call( "Vapor_cilindre_dreta:SetEmitterActive", 1 );
 Call( "Vapor_cilindre_esquerra:SetEmitterActive", 1 );
 
 if Call( "*:ControlExists", "CylinderCock", 0 ) ~= zero then
 purgValue = Call( "*:GetControlValue", "CylinderCock", 0 );
 if purgValue > 0 then
 Call( "Vapor_purga_dreta1:SetEmitterActive", 1 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 1 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 1 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 1 );
 else
 Call( "Vapor_purga_dreta1:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 0 );
 end
 end
 
 else
 Call( "Vapor_cilindre_dreta:SetEmitterActive", 0 );
 Call( "Vapor_cilindre_esquerra:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta1:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra1:SetEmitterActive", 0 );
 Call( "Vapor_purga_dreta2:SetEmitterActive", 0 );
 Call( "Vapor_purga_esquerra2:SetEmitterActive", 0 );
 end
 
 end
 
 end
 
Naturalmente, esos 
emitters han de estar declarados como 
S Child en el 
blueprint de la máquina, en la sección 
Container component y puestos en su sitio con el previsualizador del
 Asset Editor.
2.-Animaciones exteriores de controles que no son de tipo 
Sí/No: el regulador, la palanca de cambio de marchas, etc. Se trata que, desde el exterior, se vea moverse el regulador, la palanca de cambio de marchas y su timonería, los frenos con su cilindro timonería y zapatas, etc. de modo que las posiciones de la animación exterior 
sigan fielmente las posiciones del control de dentro de la cabina.
Por ejemplo, para  la palanca y timonería del cambio de marchas tenemos la posición inicial (0). Nótese el movimiento del contrapeso de la distribución, señalado con una flecha roja:

marcha adelante a fondo:

marcha atrás a fondo:

y una posición intermedia (41%):

Para ello, la palanca, timonería y contrapeso, es decir, todo lo que se va a mover, 
no debe formar parte del fichero 
IGS del modelo, sinó que todo ese conjunto móvil debe tener sus ficheros 
IGS y 
IA propios. Con ellos se hace un 
Anim procedural scenery blueprint en el que se declaran los ficheros 
IGS y 
IA. Ese 
blueprint se declara como 
S Child en el 
blueprint de la máquina, en la sección 
Container component y se pone en su sitio con el previsualizador del
 Asset Editor. Esa animacion 
no hay que declararla en la sección 
Render component -> Anim set del 
blueprint principal de la máquina.
El fichero a manipular ahora es el 
lua Simulation Script, el que está en la carpeta (
Simulation) de la máquina. La función que maneja el proceso es 
AddTime (Container componentID, AnimationID, time).
En mi máquina, el nombre que le he puesto al 
Container component es 
Node_Palanca_marxes y, en 
su blueprint, el nombre de la animación es 
Anim_palanca_marxes. Así pues, el uso de la función será 
- Código: Seleccionar todo
- Call("Node_Palanca_marxes:AddTime", "Anim_palanca_marxes", rvIncrement );
(
rvincrement  es el tiempo que damos a la animación para que se mueva durante el mismo)
Empezamos por definir una variable global, 
gPrevReverserValue, y poner la palanca justo en su punto medio:
- Código: Seleccionar todo
-    gPrevReverserValue   = 0
 
 if Call( "*:ControlExists", "Reverser", 0 ) ~= zero then
 
 Call("Node_Palanca_marxes:AddTime", "Anim_palanca_marxes", 0.29 );
 
 end
Ese código va dentro de la función 
Setup () del fichero 
lua Simulation Script. El valor 0.29 del tiempo se encuentra por experimentación... 
 
 El movimiento se controla mediante este código en la función 
Update (interval) de ese mismo script:
- Código: Seleccionar todo
-    io.output("sortida.txt")
 local rvIncrement = 0;
 
 -- Reverser
 if Call( "*:ControlExists", "Reverser", 0 ) ~= zero then
 
 controlValue = Call( "*:GetControlValue", "Reverser", 0 )
 
 if controlValue ~= gPrevReverserValue then
 
 rvIncrement = (controlValue - gPrevReverserValue)*0.333333;
 --            io.write( "DEBUG: gPrevReverserValue = ", gPrevReverserValue, ", ReverserValue = ", controlValue, ", rvIncrement = ", rvIncrement, "\n" );
 Call("Node_Palanca_marxes:AddTime", "Anim_palanca_marxes", rvIncrement );
 Call( "*:SetEngineValue", "Reverser", controlValue )
 gPrevReverserValue = controlValue;
 
 end
 
 end
 
La instrucción 
io.output("sortida.txt") abre el fichero 
sortida.txt, que aparecerá en la carpeta raíz de 
RailWorks y la instrucción 
io.write( "DEBUG: gPrevReverserValue = ", gPrevReverserValue, ", ReverserValue = ", controlValue, ", rvIncrement = ", rvIncrement, "\n" ); escribe en este fichero, con el propósito de 
debuggear el 
script. Cuando estemos satisfechos con los resultados, podemos 
comentar (las dos rayitas -- del principio de la línea) esa línea para que no se gasten recursos en el funcionamiento del juego.
El freno de mano también puede animarse del mismo modo:
Posición de desfrenado:

y su correspondencia en la vista interior de la cabina:

Posición de frenado:

y esa posición en la cabina:

Una posición intermedia:

El fichero 
script de simulación, queda así:
- Código: Seleccionar todo
- ------------------------------------------------------------
 -- Ficher de simulació per a les locomotores CGFC 27-42, "Bergues"
 ------------------------------------------------------------
 
 
 ------------------------------------------------------------
 -- Setup
 ------------------------------------------------------------
 -- Called when the engine script is initialised
 ------------------------------------------------------------
 
 function Setup ()
 
 DEBUG = true
 io.output("sortida.txt")
 
 SG_STATE_STOPPED  = 0
 SG_STATE_FORWARD  = 1
 SG_STATE_BACKWARD = 2
 
 mStopGoState = SG_STATE_STOPPED
 
 gPrevFireBoxDoorValue   = -1 -- Forces 1st frame update
 gPrevReverserValue   = 0
 gPrevRegulatorValue   = 0
 gPrevHornValue      = 0
 gPrevHandBrakeValue    = 0
 
 if Call( "*:ControlExists", "Reverser", 0 ) ~= zero then
 
 Call("Node_Palanca_marxes:AddTime", "Anim_palanca_marxes", 0.29 );
 
 end
 
 end
 
 ------------------------------------------------------------
 -- Update
 ------------------------------------------------------------
 -- Called every frame to update the simulation
 ------------------------------------------------------------
 -- Parameters:
 --   interval = time since last update
 ------------------------------------------------------------
 
 function Update (interval)
 
 local fbValue = 0;
 local sgValue = 0;
 local hbValue = 0;
 local zero = 0
 
 local hbIncrement = 0;
 local rvIncrement = 0;
 local hbValue = 0;
 local que = 0
 
 if Call( "*:ControlExists", "HandBrake", 0 ) ~= zero then
 
 hbValue = Call( "*:GetControlValue", "HandBrake", 0 );
 
 if hbValue ~= gPrevHandBrakeValue then
 
 hbIncrement = (hbValue - gPrevHandBrakeValue)*0.5;
 que=que+hbIncrement;
 --            io.write( "DEBUG: gPrevHandBrakeValue = ", gPrevHandBrakeValue, ", HandBrakeValue = ", hbValue, ", hbIncrement = ", hbIncrement, " acumulat = ", que, "\n" );
 Call("Node_Palanca_i_espiga_fre_manual:AddTime", "Anim_palanca_i_espiga_fre_manual", hbIncrement );
 Call("Node_Timoneria_frens:AddTime", "Anim_timoneria_frens", hbIncrement );
 gPrevHandBrakeValue = hbValue
 
 end
 
 end
 
 
 if Call( "*:ControlExists", "Firebox door", 0 ) ~= zero then
 
 fbValue = Call( "*:GetControlValue", "Firebox door", 0 );
 
 Call( "*:SetEmitterColour", fbValue, fbValue, fbValue );
 
 if fbValue ~= gPrevFireBoxDoorValue then
 Call( "ControlSound:SetParameter", "FireBoxDoorValue", fbValue )
 gPrevFireBoxDoorValue = fbValue
 end
 end
 
 -- Stop go controls...
 
 if Call( "*:ControlExists", "StopGoForward", 0 ) ~= zero then
 sgValue = Call( "*:GetControlValue", "StopGoForward", 0 );
 if sgValue > zero then
 mStopGoState = SG_STATE_FORWARD
 elseif sgValue < zero then
 mStopGoState = SG_STATE_BACKWARD
 end
 end
 
 
 if Call( "*:ControlExists", "StopGoStop", 0 ) ~= zero then
 sgValue = Call( "*:GetControlValue", "StopGoStop", 0 );
 if sgValue ~= zero then
 Call( "*:SetControlValue", "StopGoForward", zero, zero );
 mStopGoState = SG_STATE_STOP
 end
 end
 
 
 
 Call("SetFailureValue", "ElectricSim", "ForceMultiplierDueToFailure", 0.5)
 
 
 local controlValue = 0;
 
 -- Reverser
 if Call( "*:ControlExists", "Reverser", 0 ) ~= zero then
 
 controlValue = Call( "*:GetControlValue", "Reverser", 0 )
 
 if controlValue ~= gPrevReverserValue then
 
 rvIncrement = (controlValue - gPrevReverserValue)*0.333333;
 --            io.write( "DEBUG: gPrevReverserValue = ", gPrevReverserValue, ", ReverserValue = ", controlValue, ", rvIncrement = ", rvIncrement, "\n" );
 Call("Node_Palanca_marxes:AddTime", "Anim_palanca_marxes", rvIncrement );
 Call( "*:SetEngineValue", "Reverser", controlValue )
 gPrevReverserValue = controlValue;
 
 end
 
 end
 
 -- Regulator
 if Call( "*:ControlExists", "Regulator", 0 ) ~= zero then
 
 controlValue = Call( "*:GetControlValue", "Regulator", 0 )
 
 if controlValue ~= gPrevRegulatorValue then
 
 Call( "*:SetEngineValue", "Regulator", controlValue )
 
 gPrevRegulatorValue = controlValue
 
 end
 
 end
 
 -- Horn
 if Call( "*:ControlExists", "Horn", 0 ) ~= zero then
 
 controlValue = Call( "*:GetControlValue", "Horn", 0 )
 
 -- KJM Print("controlValue = ", controlValue);
 
 if controlValue ~= gPrevHornValue then
 
 --Call( "*:SetEngineValue", "Horn", controlValue )
 
 Call( "ControlSound:SetParameter", "HornValue", controlValue )
 
 gPrevHornValue = controlValue
 
 end
 
 end
 
 end
 
 function GetStopGoState()
 return mStopGoState
 end
 
Espero haber ayudado a avanzar en la simulación.
Saludos,
Carles Romero, 
"Brill"