Sunday 8 February 2015

TS2015 - Scenario Scripting in LUA Part 6 - Speed Checking

In addition to providing various visual interactions such as pop-ups and cinematics, you can also use the LUA scripting logic to provide for additional gameplay features as well.

One such feature you can quickly use to great effect is to use the scripting to monitor the players speed and then react to it in some way.

There are a couple of uses for this, one is to see whether the player has got the train moving - and if so perhaps congratulate them and give them some next instructions.

Another is to see if the player has stopped, in which case you might want to remind them how to get moving again.  This is particularly useful if you're aiming your scenario at newcomers to the game!

The final one and perhaps the most useful generally is to check for going too fast according to some additional rules (beyond speed limits and so forth).  For example, while the speed limit on a particular stretch of line might be 70mph, if the train you're hauling has wagons with a maximum 40mph speed limit (perhaps they're sensitive or dangerous cargo, or simply old and not suitable for higher speeds for example) then it would be good to be able to factor this additional rule in to the scenario, rather than simply advising the user at the start and then just assuming they adhere to a guideline they probably forgot 10 minutes in to the scenario.

How do you get the speed?


This is the easy bit, getting the speed can be done with a simple single command:

speed = math.abs(SysCall("PlayerEngine:GetSpeed"))

Here, we call a command PlayerEngine:GetSpeed in the game core and then run it by a math function called "abs" - what this will do is ensure that the result is always positive.  That way no matter if you're going forwards or backwards, the speed returned is always a positive number.  The result is then stored in a variable called "speed".

Note that the units returned by GetSpeed are in meters per second, NOT, miles or kilometers per hour.

What I like to do is add some more constants to the section of definitions at the top of the script to help with these calculations:

MPH = 2.23693629
KMH = 3.6

gSpeedUnits = MPH
I can now adjust the above GetSpeed example as follows:

speed = math.abs(SysCall("PlayerEngine:GetSpeed")) * gSpeedUnits

Now "speed" will contain the current speed of the loco in miles per hour.  It might at first seem odd to have "gSpeedUnits" - why not just use the constant "MPH" ?  You could do that - but if you wanted to re-use components of your script in a future script and that needed to be in KMH then you would need to do a careful search and replace to swap "MPH" to "KMH" - by using "gSpeedUnits" everywhere you can simply change the assignment to gSpeedUnits at the top of the script and the rest will simply fall in to place.

Where does it go in the script?


You will generally want to check speed as part of a condition so that you can do it regularly.  You could check to see if you're at a particular speed as part of a one-off check in an event if you wish but there aren't many cases where you really want to do that.

Let's set up a small example to check if the player has started moving yet, triggered as part of the first trigger instruction in a scenario:

function OnEvent(event)
  _G["OnEvent" .. event]();
end

function TestCondition(condition)
  _G["TestCondition" .. condition]();
end

function OnEventStartMovingCheck()
  SysCall ( "ScenarioManager:BeginConditionCheck", "AreWeMovingYet" );
end

function TestConditionAreWeMovingYet()
  speed = math.abs(SysCall("PlayerEngine:GetSpeed")) * gSpeedUnits;
  if (speed > 1) then
    SysCall ( "ScenarioManager:ShowInfoMessageExt", "You're moving!", "moving.html", 10, MSG_TOP + MSG_TOP, MSG_SMALL, TRUE );
    return CONDITION_SUCCEEDED;
  end
  return CONDITION_NOT_YET_MET;
end

Ok, lots to see there, but most of it you've already seen in previous articles, i've just included it to provide context.

So in our scenario set-up in the timetable view of the game, we've added a trigger instruction that fires an event "StartMovingCheck".  This results in a call on OnEvent with event set to "StartMovingCheck". That in turn calls OnEventStartMovingCheck.

OnEventStartMovingCheck will begin a condition test called "AreWeMovingYet".  As of that moment, the game will begin firing calls to TestCondition with "condition" set to "AreWeMovingYet" and it in turn will call TestConditionAreWeMovingYet.

Ok, so we made it to the actual important bit of this example finally!

TestConditionAreWeMovingYet, which is being called very regularly now over and over, will obtain the speed of the player.  If the speed is more than 1mph then it will open an HTML message box that loads "moving.html" and might give the player some feedback now they're moving and perhaps more instructions.  The return of "condition succeeded" means it will now no longer perform this check, so if the player stops and starts moving again it will NOT do this again.

You can see that it would be easy to modify this example to check if the player is stopped, however in that case I would strongly recommend checking for speed<0.1 rather than speed == 0 simply because of rounding and physics behaviours in the game.

This has been a simple over view on how to check the speed, you should be able now to see how you can apply it to various different cases and provide some additional gameplay behaviours.

To give you some examples, there are some scenarios on Western Lines of Scotland where you are hauling tanker wagons and these have reduced speed limits.  The storyline of the scenario is that you're riding with a fireman and guard who are very cautious of accidents.  Even though the line speed is high, the train is in some cases only rated for 30mph maximum speed.  As you drive along, the speed is constantly being checked and if you ever exceed 30mph you get a warning to slow down, after two or three warnings then the brakes will be applied to bring the train to a stop.  If you then continue to cause this to happen more times then eventually the scenario is actually terminated early, resulting in a fail.  Of course you need to tune how flexible the scripts are - if they bang you out with a scenario failure for one error then that really does qualify as a super hard (possibly super frustrating!) scenario and you should set things up to match the level you're trying to achieve.






2 comments:

  1. Hi Matt - Trying to create a speed limit warning, have managed to get a warning to appear that the driver is exceeding the speed limit but I cannot clear the message...as soon as I hit X the message re-appears....this happens continuously and I have to abort the scenario, any idea what I am doing wrong?

    thanks,

    Andy

    ReplyDelete
  2. Hi Matt, having same issue as Andy, above. Could you fix the code please?

    ReplyDelete