Route indicator board using AndiS scripts

Route indicator board using AndiS scripts

Postby albinopigeon » Sun Feb 20, 2022 1:05 pm

Hi all,

I'm looking into making a route indicator board for my signal pack, which displays a different number (1 through 6) depending on which road is set. In the case of Bournemouth West (which I am making this signal for), you effectively have three arms shared between the platform roads: a home arm, a calling-on arm, and the route indicator. The only caveat is that while the home and call-on arm must show for any of the six platform roads, the route indicator must be correct for each of the roads. Since multiple dolls cannot share the same track link, these would all therefore have to be on the same doll; but I'm unsure of how to implement it. I had a poke around the .xml file for the same signal Brinton made for the SSS Southampton - Weymouth route, but I couldn't find anything beyond a compiled script for the signal and the fact that the route indicator has one pair of animations per number.

Image

Any ideas?

Thanks in advance!
Image

Asset maker, wagon builder, route builder among others.
albinopigeon
Fit for Firing Duties
 
Posts: 34
Joined: Wed Dec 08, 2021 4:19 pm
Has thanked: 2 times
Been thanked: 1 time

Re: Route indicator board using AndiS scripts

Postby AndiS » Sun Feb 20, 2022 5:51 pm

Animated route indicators and banners are those things where I skimped back than as there were no shapes in the original Kuju signal and I had this idea of easy deployment, plus frankly speaking time was running out.

Every now and then I get reminded about this but I never reached completion in my efforts to close the gap.

In the case of animated route indicators, you can get away with the following trick, I hope.

It assumes that animation duration is the same for the indicator and the stop arm.

Each number on the indicator is an arm just as per prototype.

The stop arm is the co-acting arm for every route (ARM_COACT). Or the indications are co-acting and the stop arm is ARM_HOME.

It sounds crazy to have one arm child in 3 entries for 3 routes shown on the indicator. But it works because I play the closing animation to the end before starting the opening animation.

Things get hairy when there is a subsidiary arm involved. I am not sure how to get this going, so I hope the indicator belongs to the arm on the left. :?

What always works is a node based indicator where you switch indications on and off like signal lights of a text-based one like that on Riviera of the Fifties, or the German route indicators by Kuju (Indicator.bin in Assets/FPKuju). They are external in the sense that they are single link signal objects of their own, which gives them their own ID where you can stuff in configuration details.
AndiS
Top Link Driver!
 
Posts: 736
Joined: Wed Apr 09, 2014 5:48 pm
Has thanked: 268 times
Been thanked: 308 times

Re: Route indicator board using AndiS scripts

Postby albinopigeon » Sun Feb 20, 2022 6:33 pm

The external indicator seems like the best way to go, I'll have a look into how they might work. I'll have a good rummage around the RL50s ones to see how they were set up. I expect that it won't go totally well so there will probably be another reply in this thread in due time! The Brinton one has it coded as a standalone distant arm and I have a hunch that the script for that signal sorts it out, but alas I can't see the original .lua file.
Image

Asset maker, wagon builder, route builder among others.
albinopigeon
Fit for Firing Duties
 
Posts: 34
Joined: Wed Dec 08, 2021 4:19 pm
Has thanked: 2 times
Been thanked: 1 time

Re: Route indicator board using AndiS scripts

Postby AndiS » Sun Feb 20, 2022 10:48 pm

Nothing you find in Mark Brinton's scripts will help as his signals don't communicate with mine.

If you feel courageous, you can spend the time on combining the External Arm with the External Text-based Indicator.

Just copy the text here to separate files for a start. (The forum does not like .lua attachments in posts. But it has this "copy all" links in code blocks that is really handy.)

FP External Arms.lua:
Code: Select all
--------------------------------------------------------------------------------------
-- One or more arms (or disks) in a separate slave object that solely moves as ordered by the master stop signal
-- They are specified in a two-dimensional array with dimensions doll number and
-- SEM_CHILD_NAME/SEM_PROCEED_ANIM/SEM_BLOCKED_ANIM. This should facilitate copying over from standard signals
-- It is the same arrangement as for splitting distants.
-- Call-on functionality is not supported currently.
-- Configuration, i.e., remapping of dolls is not supported. They can be remapped by the virtual signal
-- controlling this external signal

require "Assets/AndiS/FPSignals/scripts/Shared by All.out"

function DefaultInitialise ()
   
   InitialiseId()

--   HardDebug("DefaultInitialise", "starting")
   InitialiseConstantsAndArrays()
   MAX_HOME               = 9
   
   gConnectedLink = 0
   
                              -- initialise it the old way for backward compatibility
   gArmTable = {}
   for i = 1, 9 do
      gArmTable[i] = {}
      gArmTable[i][SEM_PROCEED_ANIM] = "Clear"
      gArmTable[i][SEM_BLOCKED_ANIM] = "Stop"
      
      gArmState[i] = -1
   end
   
   armMotion = false
   armsInMotion = 0

   gInitialised         = false               -- has the route finished loading yet?
   Call( "BeginUpdate" )
   
   ResetSignalState ()
   
--   HardDebug("DefaultInitialise", "done")
end

function ResetSignalState ( )
   gInitialised = false                  -- induce resending of SIGNAL_I_AM_ARM
end

function ArmExists(dollIndex)
   if gArmTable[dollIndex] == nil then
      return false
   else
      return gArmTable[dollIndex][SEM_CHILD_NAME] ~= nil
   end
end

function SetArmState(newState, routeIndex)
   -- Initial checks: Make sure the arm exists before trying to do anything to it!
   -- Plus: be sure that this call is not just redundant
   -- Base copied from semaphore distant
   
   if ArmExists(routeIndex) then                  -- currently redundant check
      if newState == gArmState[routeIndex] then
         RasterLog("SetArmState", routeIndex .. " " .. printNil(gArmState[routeIndex]) .. " = " ..
               printNil(newState) .. " nothing to do")
      else
         RasterLog("SetArmState", animName(newState) .. " " .. routeIndex .. "  " .. printNil(gArmState[routeIndex]) ..
               " -> " ..
               printNil(newState) .. "  " .. printBoolean(armMotion) .. " " .. printNil(armsInMotion))
         if newState == ANIMSTATE_ANIMTOOPEN then                           -- want to open
            if gArmState[routeIndex] == ANIMSTATE_CLOSED then          -- normal case: closed now
               armsInMotion = armsInMotion + 1
               if not armMotion then                                    -- first arm animation
                  UpdateLog("SetArmState", "BeginUpdate" )
                  Call( "BeginUpdate" )
               end
               gArmState[routeIndex] = ANIMSTATE_ANIMTOOPEN
               if startOpening ~= nil then
                  startOpening(routeIndex)
               end
            elseif gArmState[routeIndex] == ANIMSTATE_ANIMTOCLOSED then    -- currently closing -> gross error
--               if gInitialised then
                  ErrorPrint ("SetArmState", "gArmState[" .. routeIndex .. "] starting to open while closing")                  -- reset as an emergency measure
--                   gArmState[routeIndex] = ANIMSTATE_ANIMTOOPEN
--                   return false
--                else
                  gArmState[routeIndex] = ANIMSTATE_ANIMTOOPEN         -- before actually running, overwriting does not hurt
--                end
            end                                                      -- if ANIMSTATE_OPEN or ANIMSTATE_ANIMTOOPEN,
                                                                  -- no action is required
         elseif newState == ANIMSTATE_ANIMTOCLOSED then                        -- want to close
            if gArmState[routeIndex] == ANIMSTATE_OPEN then             -- normal case: closed now
               armsInMotion = armsInMotion + 1
               if not armMotion then                                    -- first arm animation
                  UpdateLog("SetArmState", "BeginUpdate" )
                  Call( "BeginUpdate" )
               end
               gArmState[routeIndex] = ANIMSTATE_ANIMTOCLOSED
               if startClosing ~= nil then
                  startClosing(routeIndex)
               end
            elseif gArmState[routeIndex] == ANIMSTATE_ANIMTOOPEN then    -- currently opening -> gross error
--                if gInitialised then
                  ErrorPrint ("SetArmState", "gArmState[" .. routeIndex .. "] starting to close while opening")                  -- reset as an emergency measure
--                   gArmState[routeIndex] = ANIMSTATE_ANIMTOCLOSED
--                   return false
--                else
                  gArmState[routeIndex] = ANIMSTATE_ANIMTOCLOSED      -- before actually running, overwriting does not hurt
--                end
            end                                                      -- if ANIMSTATE_CLOSED or ANIMSTATE_ANIMTOCLOSED,
                                                                  -- no action is required
         elseif newState == ANIMSTATE_OPEN then                              -- opening complete
            if gArmState[routeIndex] == ANIMSTATE_ANIMTOOPEN then         -- normal case: open now
               armsInMotion = armsInMotion - 1                              -- reaction to armsInMotion == 0 in Update
            elseif gArmState[routeIndex] ~= nil
            and gArmState[routeIndex] ~= ANIMSTATE_OPEN then             
               -- could be a result of quick changes to signal state
               ErrorPrint ("SetArmState", "gArmState[" .. routeIndex .. "] ~= ANIMSTATE_ANIMTOOPEN")
               return false
            end
            gArmState[routeIndex] = ANIMSTATE_OPEN
            if finishOpening ~= nil then
               finishOpening(routeIndex)
            end
            
         elseif newState == ANIMSTATE_CLOSED then                           --closing complete
            if gArmState[routeIndex] == ANIMSTATE_ANIMTOCLOSED then      -- normal case: closed now
               armsInMotion = armsInMotion - 1                              -- reaction to armsInMotion == 0 in Update
            elseif gArmState[routeIndex] ~= ANIMSTATE_CLOSED
            and gArmState[routeIndex] ~= nil then                   -- bad surprise
               ErrorPrint ("SetArmState", "gArmState[" .. routeIndex .. "] ~= ANIMSTATE_ANIMTOCLOSED")
               return false
            end
            gArmState[routeIndex] = ANIMSTATE_CLOSED
            if finishClosing ~= nil then
               finishClosing(routeIndex)
            end
         end
      end
   else
      RasterLog("SetArmState", routeIndex .. " arm does not exist")
   end
end

function OnConsistPass ( prevFrontDist, prevBackDist, frontDist, backDist, linkIndex )
   -- don't care about trains
end


--------------------------------------------------------------------------------------
-- UPDATE
--
function Update (time)
   if gInitialised then         -- normal operation: carry on the animation until it is complete, then set animState accordingly
      if armsInMotion == 0 then                  -- no animations running
         if armMotion then                     -- we were active until recently
            armMotion = false               -- nothing more to do
         end                                 -- if armMotion is false, Update is called for another reason
      else                                 -- step along the current animations
         armMotion = true                     -- remember we are animating for the time point when we just completed it
         for i = 1, MAX_DISTANT do
            if ArmExists(i) then
               local animState = gArmState[i]
               if (animState == ANIMSTATE_ANIMTOOPEN) then
                  Call( gArmTable[i][SEM_CHILD_NAME] .. ":Reset", gArmTable[i][SEM_BLOCKED_ANIM] )
                  if Call( gArmTable[i][SEM_CHILD_NAME] .. ":AddTime", gArmTable[i][SEM_PROCEED_ANIM], time ) ~= 0 then
                     SetArmState( ANIMSTATE_OPEN, i )
                  end
               elseif (animState == ANIMSTATE_ANIMTOCLOSED) then
                  Call( gArmTable[i][SEM_CHILD_NAME] .. ":Reset", gArmTable[i][SEM_PROCEED_ANIM] )
                  if Call( gArmTable[i][SEM_CHILD_NAME] .. ":AddTime", gArmTable[i][SEM_BLOCKED_ANIM], time ) ~= 0 then
                     SetArmState( ANIMSTATE_CLOSED, i )
                  end
               end
            end
         end
      end
   else                     -- first call to Update
      
      --SimpleSendTestMessageWithinLinks()      -- makes assumptions about numbered links that are not true
      
      for i = 1, MAX_DISTANT do
         if ArmExists(i) then
            Call( gArmTable[i][SEM_CHILD_NAME] .. ":Reset", gArmTable[i][SEM_BLOCKED_ANIM] )
            Call( gArmTable[i][SEM_CHILD_NAME] .. ":AddTime", gArmTable[i][SEM_PROCEED_ANIM], 0 )
            gArmState[i] = ANIMSTATE_CLOSED
         end
      end
      -- not sure this is neeeded:
      -- Call("*:Reset", stopAnim )
      -- Call( "*:AddTime", clearAnim, 0 )
      -- gAnimState = ANIMSTATE_CLOSED
      
      -- not in distant - forgotten there?
      -- anyone still using this ??
      -- if yes, add index and loop
      if finishClosing ~= nil then                     -- needed to get the lights right initially (for AP)
         for i = 1, MAX_DISTANT do
            finishClosing(i)
         end
      end
      
      indices = ""
      for dollIndex=1, 9 do
         if ArmExists(dollIndex) then
            indices = indices .. dollIndex
         end
      end
      for link = 0, gLinkCount-1 do
         SendBackwardFromLink(SIGNAL_I_AM_ARM, indices, link)
      end
      gInitialised = true
   end
   if armsInMotion == 0 and not armMotion and gInitialised then
      UpdateLog("Animations", "EndUpdate" )
      Call( "EndUpdate" )
   end
end


-------------------------------------------------------------------------------------
-- ON SIGNAL MESSAGE
-- Shunt signals just forward the signal messages on to other signals
--
function OnSignalMessage( message, parameter, direction, linkIndex )
   MessageLog( message, parameter, direction, linkIndex )

   if (message == RESET_SIGNAL_STATE) then
      ResetSignalState()
      
   elseif (message == OPEN_ARM) then
      if ArmExists(0 + parameter) then
         SetArmState(ANIMSTATE_ANIMTOOPEN, 0 + parameter)
      else
         Call( "SendSignalMessage", message, parameter, -direction, 1, linkIndex )
      end
      
   elseif (message == CLOSE_ARM) then
      if ArmExists(0 + parameter) then
         SetArmState(ANIMSTATE_ANIMTOCLOSED, 0 + parameter)
      else
         Call( "SendSignalMessage", message, parameter, -direction, 1, linkIndex )
      end
      
   elseif message == OPEN_LOG then
      OpenLog(parameter, direction, linkIndex)
      
   -- Ignore initialisation messages from trains straddling a link - these will have the "DoNotForward" parameter
   -- Otherwise forward on the message
   -- this includes TEST_MESSAGE which is not treated in any way here
   elseif parameter ~= "DoNotForward" then
      Call( "SendSignalMessage", message, parameter, -direction, 1, linkIndex )
   end
end


FP External Text-based indicator.lua:

Code: Select all
--------------------------------------------------------------------------------------
-- A theater box type indicator in a separate slave object controlled by the master stop signal
-- The current implementation is based on texture set switching.
--

require "Assets/AndiS/FPSignals/scripts/Shared by All.out"

function Initialise ()
   
   InitialiseId()

   InitialiseConstantsAndArrays()

   gConnectedLink = 0
   gAnimState = -1
   
   gInitialised         = false               -- has the route finished loading yet?
   
   ResetSignalState ()
end

function ResetSignalState ( )
   
   gId = Call ("GetId")                  -- TODO: insert parsing for special codes in ID here
   if gId == nil or gId == "" then
      ErrorPrint("ResetSignalState", "Indicator without ID shows nothing")
      gInitialised = true                  -- do nothing on update (a bit redundant since BeginUpdate is not called)
   else
      gInitialised = false               -- induce resending of SIGNAL_I_AM_ARM
      Call( "BeginUpdate" )
   end
end

function OnConsistPass ( prevFrontDist, prevBackDist, frontDist, backDist, linkIndex )
   -- don't care about trains
end


--------------------------------------------------------------------------------------
-- UPDATE
--
function Update (time)
   if gInitialised then

   else
      SimpleSendTestMessageWithinLinks()
      
      -- new 21 Sept 2017
      if closedCharacter == nil then
         Call( "SetText", "", 0 )
      else
         Call( "SetText", closedCharacter, 0 )
      end
      
      Call( "SendSignalMessage", SIGNAL_I_AM_INDICATOR, gId, -1, 1, 0 )
      
      gInitialised = true
      Call ("EndUpdate")
   end
end

-------------------------------------------------------------------------------------
-- ON SIGNAL MESSAGE
-- Shunt signals just forward the signal messages on to other signals
--
function OnSignalMessage( message, parameter, direction, linkIndex )
   MessageLog( message, parameter, direction, linkIndex )

   if (message == RESET_SIGNAL_STATE) then
      ResetSignalState()
      
   elseif (message == SHOW) then
      -- new 21 Sept 2017
      if closedCharacter == nil or parameter ~= "" then
         Call( "SetText", parameter, 0 )
      else
         Call( "SetText", closedCharacter, 0 )
      end
         
   elseif message == OPEN_LOG then
      OpenLog(parameter, direction, linkIndex)
      
   -- Ignore initialisation messages from trains straddling a link - these will have the "DoNotForward" parameter
   -- Otherwise forward on the message
   elseif parameter ~= "DoNotForward" then
      Call( "SendSignalMessage", message, parameter, -direction, 1, linkIndex )
   end
   
end


The external arms is an afterthought for those cases, where a signal reads to two parallel tracks. Since you can only have one unnumbered link 0, you need two invisible "master signals" in the two tracks and a "slave signal" that is the External Arms that has a link in each track and listens for commands that to do and animate the arms of a single signal shape placed somewhere in proximity of the scene. I only spell this out so you know why you should ignore most of the stuff that does not make sense now.

The aim is a external arms script that interfaces with the main signal like an indicator, not like the external arms script that is the starting point.

The indicator script is dead simple, but it is crucial to keep all the lines except those mentioned below to maintain all sorts of functionalities not discussed here.

We start from the indicator script.

In function Update, we keep almost nothing:

Code: Select all
function Update (time)
   if gInitialised then

-- big chunk of copied code will be inserted here !

   else
      SimpleSendTestMessageWithinLinks()

-- small chunk of copied code goes here
      
      Call( "SendSignalMessage", SIGNAL_I_AM_INDICATOR, gId, -1, 1, 0 )
      
      gInitialised = true
      Call ("EndUpdate")
   end
-- extra stuff at end goes here
end


The small chunk is quite simple. In the External Arms script, we steal this:

Code: Select all
      for i = 1, MAX_DISTANT do
         if ArmExists(i) then
            Call( gArmTable[i][SEM_CHILD_NAME] .. ":Reset", gArmTable[i][SEM_BLOCKED_ANIM] )
            Call( gArmTable[i][SEM_CHILD_NAME] .. ":AddTime", gArmTable[i][SEM_PROCEED_ANIM], 0 )
            gArmState[i] = ANIMSTATE_CLOSED
         end
      end

-- the following is an afterthought explained in the end
      indices = ""
      for dollIndex=1, 9 do
         if ArmExists(dollIndex) then
            indices = indices .. dollIndex
         end
      end


I believe it is all you need there.

The "extra stuff at end" is this little bit:
Code: Select all
   if armsInMotion == 0 and not armMotion and gInitialised then
      UpdateLog("Animations", "EndUpdate" )
      Call( "EndUpdate" )
   end

I go some extra mile to get rid of some edge cases. I forgot the edge cases, I just remember the frustration. :P

Now for the big chunk that operates the arms. As you may have read in the original developer documentation, Update gets called often so you can inch the animations forward. This is done in a special Kuju way that I don't want to comment on.

The big chunk reads like this:
Code: Select all
      if armsInMotion == 0 then                  -- no animations running
         if armMotion then                     -- we were active until recently
            armMotion = false               -- nothing more to do
         end                                 -- if armMotion is false, Update is called for another reason
      else                                 -- step along the current animations
         armMotion = true                     -- remember we are animating for the time point when we just completed it
         for i = 1, MAX_DISTANT do
            if ArmExists(i) then
               local animState = gArmState[i]
               if (animState == ANIMSTATE_ANIMTOOPEN) then
                  Call( gArmTable[i][SEM_CHILD_NAME] .. ":Reset", gArmTable[i][SEM_BLOCKED_ANIM] )
                  if Call( gArmTable[i][SEM_CHILD_NAME] .. ":AddTime", gArmTable[i][SEM_PROCEED_ANIM], time ) ~= 0 then
                     SetArmState( ANIMSTATE_OPEN, i )
                  end
               elseif (animState == ANIMSTATE_ANIMTOCLOSED) then
                  Call( gArmTable[i][SEM_CHILD_NAME] .. ":Reset", gArmTable[i][SEM_PROCEED_ANIM] )
                  if Call( gArmTable[i][SEM_CHILD_NAME] .. ":AddTime", gArmTable[i][SEM_BLOCKED_ANIM], time ) ~= 0 then
                     SetArmState( ANIMSTATE_CLOSED, i )
                  end
               end
            end
         end
      end


Next we copy functions ArmExists and SetArmState. It does not really matter where you put them but in the source files the are found before function Update.

I am not too sure about ResetSignalState, so I would keep the version in the indicator script.

Function Initialise shows a big difference. We need the style found in the indicator script, that is function Initialise. I would just copy over this block:
Code: Select all
   gArmTable = {}
   for i = 1, 9 do
      gArmTable[i] = {}
      gArmTable[i][SEM_PROCEED_ANIM] = "Clear"
      gArmTable[i][SEM_BLOCKED_ANIM] = "Stop"
      
      gArmState[i] = -1
   end
   
   armMotion = false
   armsInMotion = 0


Moving from function Update up was a bit chaotic but that was my biggest worry. Now checking what follows function Update, we only find OnSignalMessage. One reason why this code never got release to the public is that I would have to remember all the stop-gap solutions I stuck on top of each other to make the signals look bright when they are innocent if not clueless observers of the train movements happening around them. This shows most in all the messages sent around.

The interface of an indicator towards the main signal is fundamentally different from that of the external arms. We do not get separate messages for open and close, we only get one and if the parameter is the empty string, then we close any arm that is open.

So when we get message SHOW, we check whether parameter is unequal "". In that case, we do:
Code: Select all
      if ArmExists(0 + parameter) then
         SetArmState(ANIMSTATE_ANIMTOOPEN, 0 + parameter)
      else
         Call( "SendSignalMessage", message, parameter, -direction, 1, linkIndex )
      end

The else branch is for the case where there are several external indicators.

For closing, the indicator does not the information what to close, so we need our own variable to keep track of that. I call this shownIndication to be sure it does not clash with anything out there. It is set to the parameter on opening and set to nil after we closed.

When we get the command to close (SHOW with empty parameter), we check whether we opened anything, i.e., whether shownIndication is not nil. In that case, we close that. Otherwise we forward the message, assuming that there will be another indicator that needs closing. This is a bit wonky, but so one complained yet about signals with two indicators not working right because of my scripts. :lol:

After all this, function OnSignalMessage should look like this:
Code: Select all
function OnSignalMessage( message, parameter, direction, linkIndex )
   MessageLog( message, parameter, direction, linkIndex )

   if (message == RESET_SIGNAL_STATE) then
      ResetSignalState()
      
   elseif (message == SHOW) then
      if parameter ~= "" then
         shownIndication = parameter
         if ArmExists(0 + parameter) then
            SetArmState(ANIMSTATE_ANIMTOOPEN, 0 + parameter)
         else
            Call( "SendSignalMessage", message, parameter, -direction, 1, linkIndex )
         end
      else
         if shownIndication =~ nil then
            SetArmState(ANIMSTATE_ANIMTOCLOSED, 0 + parameter)
            shownIndication = nil
         else
            Call( "SendSignalMessage", message, parameter, -direction, 1, linkIndex )
         end
      end
         
   elseif message == OPEN_LOG then
      OpenLog(parameter, direction, linkIndex)
      
   -- Ignore initialisation messages from trains straddling a link - these will have the "DoNotForward" parameter
   -- Otherwise forward on the message
   elseif parameter ~= "DoNotForward" then
      Call( "SendSignalMessage", message, parameter, -direction, 1, linkIndex )
   end
   
end


The indicator will want you to put 123 in the signal ID if you have arms 1, 2, 3. To get rid of this, you put in the stuff marked afterthought above and you replace gId by indices in the line that follows, so it reads:
Code: Select all
      Call( "SendSignalMessage", SIGNAL_I_AM_INDICATOR, indices, -1, 1, 0 )

Then you get rid of most of function ResetSignalState so it just reads:
Code: Select all
function ResetSignalState ( )
   
      gInitialised = false
      Call( "BeginUpdate" )

end

Now the indicator finds out which arms it has and sends a message to the signal that conveys this information. This will cause the signal to only ask for indications in this list.

I enclose the result of all this here:
Code: Select all
--------------------------------------------------------------------------------------
-- A theater box type indicator in a separate slave object controlled by the master stop signal
-- The current implementation is based on texture set switching.
--

require "Assets/AndiS/FPSignals/scripts/Shared by All.out"

function Initialise ()
   
   InitialiseId()

   InitialiseConstantsAndArrays()

   -- copied over from External Arms
   gArmTable = {}
   for i = 1, 9 do
      gArmTable[i] = {}
      gArmTable[i][SEM_PROCEED_ANIM] = "Clear"
      gArmTable[i][SEM_BLOCKED_ANIM] = "Stop"
      
      gArmState[i] = -1
   end
   
   armMotion = false
   armsInMotion = 0
   -- end of copied block

   gConnectedLink = 0
   gAnimState = -1
   
   gInitialised         = false               -- has the route finished loading yet?
   
   ResetSignalState ()
end

function ResetSignalState ( )
   
      gInitialised = false
      Call( "BeginUpdate" )

end

function OnConsistPass ( prevFrontDist, prevBackDist, frontDist, backDist, linkIndex )
   -- don't care about trains
end


function ArmExists(dollIndex)
   if gArmTable[dollIndex] == nil then
      return false
   else
      return gArmTable[dollIndex][SEM_CHILD_NAME] ~= nil
   end
end

function SetArmState(newState, routeIndex)
   -- Initial checks: Make sure the arm exists before trying to do anything to it!
   -- Plus: be sure that this call is not just redundant
   -- Base copied from semaphore distant
   
   if ArmExists(routeIndex) then                  -- currently redundant check
      if newState == gArmState[routeIndex] then
         RasterLog("SetArmState", routeIndex .. " " .. printNil(gArmState[routeIndex]) .. " = " ..
               printNil(newState) .. " nothing to do")
      else
         RasterLog("SetArmState", animName(newState) .. " " .. routeIndex .. "  " .. printNil(gArmState[routeIndex]) ..
               " -> " ..
               printNil(newState) .. "  " .. printBoolean(armMotion) .. " " .. printNil(armsInMotion))
         if newState == ANIMSTATE_ANIMTOOPEN then                           -- want to open
            if gArmState[routeIndex] == ANIMSTATE_CLOSED then          -- normal case: closed now
               armsInMotion = armsInMotion + 1
               if not armMotion then                                    -- first arm animation
                  UpdateLog("SetArmState", "BeginUpdate" )
                  Call( "BeginUpdate" )
               end
               gArmState[routeIndex] = ANIMSTATE_ANIMTOOPEN
               if startOpening ~= nil then
                  startOpening(routeIndex)
               end
            elseif gArmState[routeIndex] == ANIMSTATE_ANIMTOCLOSED then    -- currently closing -> gross error
--               if gInitialised then
                  ErrorPrint ("SetArmState", "gArmState[" .. routeIndex .. "] starting to open while closing")                  -- reset as an emergency measure
--                   gArmState[routeIndex] = ANIMSTATE_ANIMTOOPEN
--                   return false
--                else
                  gArmState[routeIndex] = ANIMSTATE_ANIMTOOPEN         -- before actually running, overwriting does not hurt
--                end
            end                                                      -- if ANIMSTATE_OPEN or ANIMSTATE_ANIMTOOPEN,
                                                                  -- no action is required
         elseif newState == ANIMSTATE_ANIMTOCLOSED then                        -- want to close
            if gArmState[routeIndex] == ANIMSTATE_OPEN then             -- normal case: closed now
               armsInMotion = armsInMotion + 1
               if not armMotion then                                    -- first arm animation
                  UpdateLog("SetArmState", "BeginUpdate" )
                  Call( "BeginUpdate" )
               end
               gArmState[routeIndex] = ANIMSTATE_ANIMTOCLOSED
               if startClosing ~= nil then
                  startClosing(routeIndex)
               end
            elseif gArmState[routeIndex] == ANIMSTATE_ANIMTOOPEN then    -- currently opening -> gross error
--                if gInitialised then
                  ErrorPrint ("SetArmState", "gArmState[" .. routeIndex .. "] starting to close while opening")                  -- reset as an emergency measure
--                   gArmState[routeIndex] = ANIMSTATE_ANIMTOCLOSED
--                   return false
--                else
                  gArmState[routeIndex] = ANIMSTATE_ANIMTOCLOSED      -- before actually running, overwriting does not hurt
--                end
            end                                                      -- if ANIMSTATE_CLOSED or ANIMSTATE_ANIMTOCLOSED,
                                                                  -- no action is required
         elseif newState == ANIMSTATE_OPEN then                              -- opening complete
            if gArmState[routeIndex] == ANIMSTATE_ANIMTOOPEN then         -- normal case: open now
               armsInMotion = armsInMotion - 1                              -- reaction to armsInMotion == 0 in Update
            elseif gArmState[routeIndex] ~= nil
            and gArmState[routeIndex] ~= ANIMSTATE_OPEN then             
               -- could be a result of quick changes to signal state
               ErrorPrint ("SetArmState", "gArmState[" .. routeIndex .. "] ~= ANIMSTATE_ANIMTOOPEN")
               return false
            end
            gArmState[routeIndex] = ANIMSTATE_OPEN
            if finishOpening ~= nil then
               finishOpening(routeIndex)
            end
            
         elseif newState == ANIMSTATE_CLOSED then                           --closing complete
            if gArmState[routeIndex] == ANIMSTATE_ANIMTOCLOSED then      -- normal case: closed now
               armsInMotion = armsInMotion - 1                              -- reaction to armsInMotion == 0 in Update
            elseif gArmState[routeIndex] ~= ANIMSTATE_CLOSED
            and gArmState[routeIndex] ~= nil then                   -- bad surprise
               ErrorPrint ("SetArmState", "gArmState[" .. routeIndex .. "] ~= ANIMSTATE_ANIMTOCLOSED")
               return false
            end
            gArmState[routeIndex] = ANIMSTATE_CLOSED
            if finishClosing ~= nil then
               finishClosing(routeIndex)
            end
         end
      end
   else
      RasterLog("SetArmState", routeIndex .. " arm does not exist")
   end
end


--------------------------------------------------------------------------------------
-- UPDATE
--
function Update (time)
   if gInitialised then

      -- big chunk of copied code will be inserted here !   
      if armsInMotion == 0 then                  -- no animations running
         if armMotion then                     -- we were active until recently
            armMotion = false               -- nothing more to do
         end                                 -- if armMotion is false, Update is called for another reason
      else                                 -- step along the current animations
         armMotion = true                     -- remember we are animating for the time point when we just completed it
         for i = 1, MAX_DISTANT do
            if ArmExists(i) then
               local animState = gArmState[i]
               if (animState == ANIMSTATE_ANIMTOOPEN) then
                  Call( gArmTable[i][SEM_CHILD_NAME] .. ":Reset", gArmTable[i][SEM_BLOCKED_ANIM] )
                  if Call( gArmTable[i][SEM_CHILD_NAME] .. ":AddTime", gArmTable[i][SEM_PROCEED_ANIM], time ) ~= 0 then
                     SetArmState( ANIMSTATE_OPEN, i )
                  end
               elseif (animState == ANIMSTATE_ANIMTOCLOSED) then
                  Call( gArmTable[i][SEM_CHILD_NAME] .. ":Reset", gArmTable[i][SEM_PROCEED_ANIM] )
                  if Call( gArmTable[i][SEM_CHILD_NAME] .. ":AddTime", gArmTable[i][SEM_BLOCKED_ANIM], time ) ~= 0 then
                     SetArmState( ANIMSTATE_CLOSED, i )
                  end
               end
            end
         end
      end

   else
      SimpleSendTestMessageWithinLinks()
      
      -- small chunk of copied code goes here
      for i = 1, MAX_DISTANT do
         if ArmExists(i) then
            Call( gArmTable[i][SEM_CHILD_NAME] .. ":Reset", gArmTable[i][SEM_BLOCKED_ANIM] )
            Call( gArmTable[i][SEM_CHILD_NAME] .. ":AddTime", gArmTable[i][SEM_PROCEED_ANIM], 0 )
            gArmState[i] = ANIMSTATE_CLOSED
         end
      end
            
      indices = ""
      for dollIndex=1, 9 do
         if ArmExists(dollIndex) then
            indices = indices .. dollIndex
         end
      end
      Call( "SendSignalMessage", SIGNAL_I_AM_INDICATOR, indices, -1, 1, 0 )
      
      gInitialised = true
      Call ("EndUpdate")
   end
   
   -- extra stuff at end goes here
   if armsInMotion == 0 and not armMotion and gInitialised then
      UpdateLog("Animations", "EndUpdate" )
      Call( "EndUpdate" )
   end
end

-------------------------------------------------------------------------------------
-- ON SIGNAL MESSAGE
-- Shunt signals just forward the signal messages on to other signals
--
function OnSignalMessage( message, parameter, direction, linkIndex )
   MessageLog( message, parameter, direction, linkIndex )

   if (message == RESET_SIGNAL_STATE) then
      ResetSignalState()
      
   elseif (message == SHOW) then
      if parameter ~= "" then
         shownIndication = parameter
         if ArmExists(0 + parameter) then
            SetArmState(ANIMSTATE_ANIMTOOPEN, 0 + parameter)
         else
            Call( "SendSignalMessage", message, parameter, -direction, 1, linkIndex )
         end
      else
         if shownIndication =~ nil then
            SetArmState(ANIMSTATE_ANIMTOCLOSED, 0 + parameter)
            shownIndication = nil
         else
            Call( "SendSignalMessage", message, parameter, -direction, 1, linkIndex )
         end
      end
         
   elseif message == OPEN_LOG then
      OpenLog(parameter, direction, linkIndex)
      
   -- Ignore initialisation messages from trains straddling a link - these will have the "DoNotForward" parameter
   -- Otherwise forward on the message
   elseif parameter ~= "DoNotForward" then
      Call( "SendSignalMessage", message, parameter, -direction, 1, linkIndex )
   end
   
end


I am sure there are bugs in there, and I am sure I have no clue where. But it is all I can supply at the moment.
AndiS
Top Link Driver!
 
Posts: 736
Joined: Wed Apr 09, 2014 5:48 pm
Has thanked: 268 times
Been thanked: 308 times

Re: Route indicator board using AndiS scripts

Postby albinopigeon » Sun Feb 20, 2022 11:08 pm

Goodness me, this is going to take some time to digest! But I shall give it a go once I have made the route indicator itself.

Cheers Andi! I'll give you an update here once I've implemented and tested it.
Image

Asset maker, wagon builder, route builder among others.
albinopigeon
Fit for Firing Duties
 
Posts: 34
Joined: Wed Dec 08, 2021 4:19 pm
Has thanked: 2 times
Been thanked: 1 time

Re: Route indicator board using AndiS scripts

Postby albinopigeon » Tue Feb 22, 2022 11:50 pm

I've managed to get the route indicator working, but through a completely different method. The script you posted went right over my head, but Ken/Auscg is helping me out with deciphering it.

I've coded it as a co-acting arm, however the only caveat is that when a call-on predictor is placed in front, the whole signal stops working. So that needs to be ironed out before the signal can be deemed "working", but ideally I'd like that fix to be applied to the current method I have working. Can call-on arms and co-acting arms not work together?

Image

Image

As an aside, I've gotten a double home/distant signal working, except that the sound only plays once as the arms rise - the only issue being that the Distant arm rises once the Home arm is finished rising, which therefore means that no sound accompanies it. Is there a way to get around this similar to how we worked out the shunt disc sounds?
Image

Asset maker, wagon builder, route builder among others.
albinopigeon
Fit for Firing Duties
 
Posts: 34
Joined: Wed Dec 08, 2021 4:19 pm
Has thanked: 2 times
Been thanked: 1 time

Re: Route indicator board using AndiS scripts

Postby AndiS » Wed Feb 23, 2022 1:04 am

albinopigeon wrote:Can call-on arms and co-acting arms not work together?

Basically (actually: fundamentally) no. :? The thing is: The call-on arm is cleared instead of the stop arm. The co-acting arm acts with the stop arm. There is nothing that co-acts with the call-on arm.

albinopigeon wrote:As an aside, I've gotten a double home/distant signal working, except that the sound only plays once as the arms rise - the only issue being that the Distant arm rises once the Home arm is finished rising, which therefore means that no sound accompanies it. Is there a way to get around this similar to how we worked out the shunt disc sounds?

I am not sure I understand you right. But I guess you simply need one sound child per arm. The reason for this is that each sound has its own sound parameter. When stop arm 1 opens, you change the value of the sound parameter for that one sound child to 1 and it remains so until that arm is closed. So must likely your script sets the same sound parameter to one again, but it is already one. So no sound is played as the sound only plays when the value changes.

I believe there is a way to configure the sound in another way, but all memories of sound configuration have rotten in the 10+ years since I last read into the subject. So the easy way is to have 4 child blueprints that all reference the same audio file but they all have their own parameter.

Your startOpening would then look about like this:
if dollIndex == 1 then
if armIndex == ARM_DISTANT then
Call ("ControlSoundDistant1:SetParameter", "SignalProgress", 1)
else
Call ("ControlSoundHome1:SetParameter", "SignalProgress", 1)
else
if armIndex == ARM_DISTANT then
Call ("ControlSoundDistant2:SetParameter", "SignalProgress", 1)
else
Call ("ControlSoundHome2:SetParameter", "SignalProgress", 1)
end

So you instead of ControlSound1 and 2, you now have four. Not too elegant but the elegant bit is rotten, as far as the copy in my brains is concerned.
AndiS
Top Link Driver!
 
Posts: 736
Joined: Wed Apr 09, 2014 5:48 pm
Has thanked: 268 times
Been thanked: 308 times

Re: Route indicator board using AndiS scripts

Postby albinopigeon » Wed Feb 23, 2022 9:40 am

Ah, I may have worded my response wrong. I meant to say that does the presence of both a co-acting arm and call-on arm stop a signal working altogether? Having the indicator not come on when the call-on arm rises is a caveat I can accept :D

Actually, the signal works fine with the call-on arm and the co-acting arm on the same doll. But placing a call-on indicator (FP MP Call-on I believe is the name) stops the whole thing working. Would you happen to know why?

As for the sound fix, that will probably work. I'll test it when I get the chance and hopefully return with good news!
Image

Asset maker, wagon builder, route builder among others.
albinopigeon
Fit for Firing Duties
 
Posts: 34
Joined: Wed Dec 08, 2021 4:19 pm
Has thanked: 2 times
Been thanked: 1 time

Re: Route indicator board using AndiS scripts

Postby AndiS » Wed Feb 23, 2022 11:20 am

You need to be more specific on which indicator you use. Most likely you will find a minor flaw while digging up this information. ;)

Variant 1:

I understood your gArmTable to be like this:
X Co-acting arms all controlling the one stop arm model
X home arms each controlling one indication of the route indicator
1 call-on arm for route 1 that controls the one call-on arm model.

Alternatively, you can have X call-on arm entries in gArmTable like you do for the co-acting arm. But I believe that call-on arm 1 is used for routes 2 etc. by the signal logic.

No external signal objects in this variant.

Variant 2:

You could do it like this:
1 or X stop-arm entries in gArmTable all controlling the one stop arm
1 or X call-on arm entries.

An external signal object that controls the route indicator. This would be text-based or node-based using existing scripts or animation-based if you go through the frankensteining exercise above - or if you just give the end result in the last code quote a chance and tell me what LogMate said about if, if you have the time/energy for such experiments.

I believe this is rather not what you are doing but that might be the cause I don't understand your post. :lol:
In case you use an external signal object for an indicator, you are highly unlikely to want it to have call-on functionality (by putting C in the signal ID) as you surely have the call-on arm an integral part of the main signal as you would if there were no indicator.
AndiS
Top Link Driver!
 
Posts: 736
Joined: Wed Apr 09, 2014 5:48 pm
Has thanked: 268 times
Been thanked: 308 times

Re: Route indicator board using AndiS scripts

Postby albinopigeon » Wed Feb 23, 2022 12:19 pm

This is the code I've gone for to make it work:

require "Assets/AndiS/FPSignals/scripts/FP Universal Semaphore.out"

function Initialise ()
DefaultInitialise() -- no need to give the route count here

gArmTable[1][ARM_HOME][SEM_CHILD_NAME] = "ARM_HOME1"
gArmTable[1][ARM_HOME][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[1][ARM_HOME][SEM_BLOCKED_ANIM] = "Stop01"

------------------------------------------------------------------
-- ROUTE INDICATOR ROAD 1

gArmTable[2][ARM_HOME][SEM_CHILD_NAME] = "ARM_HOME2"
gArmTable[2][ARM_HOME][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[2][ARM_HOME][SEM_BLOCKED_ANIM] = "Stop01"

gArmTable[2][ARM_CALLON][SEM_CHILD_NAME] = "ARM_CALLON1"
gArmTable[2][ARM_CALLON][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[2][ARM_CALLON][SEM_BLOCKED_ANIM] = "Stop01"

gArmTable[2][ARM_COACT][SEM_CHILD_NAME] = "ARM_DIST"
gArmTable[2][ARM_COACT][SEM_PROCEED_ANIM] = "on1"
gArmTable[2][ARM_COACT][SEM_BLOCKED_ANIM] = "off1"

------------------------------------------------------------------
-- ROUTE INDICATOR ROAD 2

gArmTable[3][ARM_HOME][SEM_CHILD_NAME] = "ARM_HOME2"
gArmTable[3][ARM_HOME][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[3][ARM_HOME][SEM_BLOCKED_ANIM] = "Stop01"

gArmTable[3][ARM_CALLON][SEM_CHILD_NAME] = "ARM_CALLON1"
gArmTable[3][ARM_CALLON][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[3][ARM_CALLON][SEM_BLOCKED_ANIM] = "Stop01"

gArmTable[3][ARM_COACT][SEM_CHILD_NAME] = "ARM_DIST"
gArmTable[3][ARM_COACT][SEM_PROCEED_ANIM] = "on2"
gArmTable[3][ARM_COACT][SEM_BLOCKED_ANIM] = "off2"

------------------------------------------------------------------
-- ROUTE INDICATOR ROAD 3

gArmTable[4][ARM_HOME][SEM_CHILD_NAME] = "ARM_HOME2"
gArmTable[4][ARM_HOME][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[4][ARM_HOME][SEM_BLOCKED_ANIM] = "Stop01"

gArmTable[4][ARM_CALLON][SEM_CHILD_NAME] = "ARM_CALLON1"
gArmTable[4][ARM_CALLON][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[4][ARM_CALLON][SEM_BLOCKED_ANIM] = "Stop01"

gArmTable[4][ARM_COACT][SEM_CHILD_NAME] = "ARM_DIST"
gArmTable[4][ARM_COACT][SEM_PROCEED_ANIM] = "on3"
gArmTable[4][ARM_COACT][SEM_BLOCKED_ANIM] = "off3"

------------------------------------------------------------------
-- ROUTE INDICATOR ROAD 4

gArmTable[5][ARM_HOME][SEM_CHILD_NAME] = "ARM_HOME2"
gArmTable[5][ARM_HOME][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[5][ARM_HOME][SEM_BLOCKED_ANIM] = "Stop01"

gArmTable[5][ARM_CALLON][SEM_CHILD_NAME] = "ARM_CALLON1"
gArmTable[5][ARM_CALLON][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[5][ARM_CALLON][SEM_BLOCKED_ANIM] = "Stop01"

gArmTable[5][ARM_COACT][SEM_CHILD_NAME] = "ARM_DIST"
gArmTable[5][ARM_COACT][SEM_PROCEED_ANIM] = "on4"
gArmTable[5][ARM_COACT][SEM_BLOCKED_ANIM] = "off4"

------------------------------------------------------------------
-- ROUTE INDICATOR ROAD 5

gArmTable[6][ARM_HOME][SEM_CHILD_NAME] = "ARM_HOME2"
gArmTable[6][ARM_HOME][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[6][ARM_HOME][SEM_BLOCKED_ANIM] = "Stop01"

gArmTable[6][ARM_CALLON][SEM_CHILD_NAME] = "ARM_CALLON1"
gArmTable[6][ARM_CALLON][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[6][ARM_CALLON][SEM_BLOCKED_ANIM] = "Stop01"

gArmTable[6][ARM_COACT][SEM_CHILD_NAME] = "ARM_DIST"
gArmTable[6][ARM_COACT][SEM_PROCEED_ANIM] = "on5"
gArmTable[6][ARM_COACT][SEM_BLOCKED_ANIM] = "off5"

------------------------------------------------------------------
-- ROUTE INDICATOR ROAD 6

gArmTable[7][ARM_HOME][SEM_CHILD_NAME] = "ARM_HOME2"
gArmTable[7][ARM_HOME][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[7][ARM_HOME][SEM_BLOCKED_ANIM] = "Stop01"

gArmTable[7][ARM_CALLON][SEM_CHILD_NAME] = "ARM_CALLON1"
gArmTable[7][ARM_CALLON][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[7][ARM_CALLON][SEM_BLOCKED_ANIM] = "Stop01"

gArmTable[7][ARM_COACT][SEM_CHILD_NAME] = "ARM_DIST"
gArmTable[7][ARM_COACT][SEM_PROCEED_ANIM] = "on6"
gArmTable[7][ARM_COACT][SEM_BLOCKED_ANIM] = "off6"

------------------------------------------------------------------

gArmTable[8][ARM_HOME][SEM_CHILD_NAME] = "ARM_HOME3"
gArmTable[8][ARM_HOME][SEM_PROCEED_ANIM] = "Clear01"
gArmTable[8][ARM_HOME][SEM_BLOCKED_ANIM] = "Stop01"

end


function startOpening(dollIndex, armIndex)

Call ("ControlSound:SetParameter", "SignalProgress", 1)

end

function startClosing(dollIndex, armIndex)

Call ("ControlSound:SetParameter", "SignalProgress", 0)

end


Hopefully that might clear some things up as to why the call-on predictor is breaking it.
Image

Asset maker, wagon builder, route builder among others.
albinopigeon
Fit for Firing Duties
 
Posts: 34
Joined: Wed Dec 08, 2021 4:19 pm
Has thanked: 2 times
Been thanked: 1 time

Next

Return to Route Creation

Who is online

Users browsing this forum: No registered users and 1 guest