One of my Trainz activities is writing scripts, sometimes for myself, but often for others. I'm rather choosy about this and only take on scripting tasks for others if the topic interests me. Generally this will be some aspect of Trainz script I have not done before. One of my preferred customers, Kieran AKA CaptainKMan, makes some unique assets so I frequently find myself helping him out.
The purpose of this particular series of blog articles is to describe my script development process so that others might learn from it. Or learn from my mistakes. :hehe:
On this particular occasion, Kieran has built two assets: a 1963 Volkswagen "Beetle" as a product and the same vehicle as traffic (carz). But, instead of having the same coloured vehicle each time, he has asked that the vehicle colour scheme be random and chosen from a texture library. So my task is to create a script for both assets and provide any changes necessary to the asset config.txt. For this Kieran has provided me with a CDP of the assets plus a couple of "helper" assets.
The target Trainz version is TS09 but I am developing the script in TS10 as it is more convenient.
Before starting a scripting task it is useful to have a test scenario. In programmer parlance this is akin to a test harness. Kieran has a flat car asset (kuid:459464:100007) that has, by default, a product called Mr Bean's Mini Product (kuid:459464:1479). Here is a small consist in Surveyor with my GWR 54XX Tank Loco, the flat car loaded with three Mr Bean Minis and a version of Andi Smith's Loco Shed.
The Mr Bean Mini Product is not the focus of the script task but serves to demonstrate that three of the same coloured vehicle is undesirable.
So, why is Andi Smith's Loco Shed in the Session?
Actually this particular asset is the result of Andi Smith's very clever scripting tutorial at http://online.ts2009.com/mediaWiki/index.php5/Getting_Started_in_TrainzScript. That asset demonstrates the use of texture replacement using a texture library and a properties browser. For our solution we don't need the properties browser but there is code in the script that can be re-used for our task.
Here is the Loco Shed script:
In Part 2 I will discuss my test asset and initial experiments.
The purpose of this particular series of blog articles is to describe my script development process so that others might learn from it. Or learn from my mistakes. :hehe:
On this particular occasion, Kieran has built two assets: a 1963 Volkswagen "Beetle" as a product and the same vehicle as traffic (carz). But, instead of having the same coloured vehicle each time, he has asked that the vehicle colour scheme be random and chosen from a texture library. So my task is to create a script for both assets and provide any changes necessary to the asset config.txt. For this Kieran has provided me with a CDP of the assets plus a couple of "helper" assets.
The target Trainz version is TS09 but I am developing the script in TS10 as it is more convenient.
Before starting a scripting task it is useful to have a test scenario. In programmer parlance this is akin to a test harness. Kieran has a flat car asset (kuid:459464:100007) that has, by default, a product called Mr Bean's Mini Product (kuid:459464:1479). Here is a small consist in Surveyor with my GWR 54XX Tank Loco, the flat car loaded with three Mr Bean Minis and a version of Andi Smith's Loco Shed.

The Mr Bean Mini Product is not the focus of the script task but serves to demonstrate that three of the same coloured vehicle is undesirable.
So, why is Andi Smith's Loco Shed in the Session?
Actually this particular asset is the result of Andi Smith's very clever scripting tutorial at http://online.ts2009.com/mediaWiki/index.php5/Getting_Started_in_TrainzScript. That asset demonstrates the use of texture replacement using a texture library and a properties browser. For our solution we don't need the properties browser but there is code in the script that can be re-used for our task.
Here is the Loco Shed script:
Code:
include "Buildable.gs"
include "Interface.gs"
/* A Loco Shed with opening doors, flashing coronas, changeable shed name, changeable wall texture, removable roof, trigger points, track attachments, saving configuration
to the Trainz database (soup) and a Property Browser variant
*/
class Tutorial isclass Buildable {
bool doorsOpen = false;
bool roofVisible;
string nameText = "None";
// Global variables to hold the texture library, skin index and description.
Asset skins;
int skin;
string skinDescription = "None";
void SetNameText(void);
public void Init(void) {
inherited();
SetFXCoronaTexture("corona",null);
SetNameText();
AddHandler(me,"Object","","ObjectHandler");
// This call sets skins, to refer to the texture-group defined in our kuid-table.
skins = GetAsset().FindAsset("skins");
}
Asset GetCorona(string mesh, string effect) {
Soup meshtable = GetAsset().GetConfigSoup().GetNamedSoup("mesh-table");
Soup effects = meshtable.GetNamedSoup(mesh).GetNamedSoup("effects");
KUID kuid = effects.GetNamedSoup(effect).GetNamedTagAsKUID("texture-kuid");
return World.FindAsset(kuid);
}
thread void RingTheBell(void) {
while (doorsOpen) {
Sleep(0.35 + World.Play2DSound(GetAsset(),"bell.wav"));
}
}
void SetNameText(void) {
if (nameText == "None" or nameText == "") {
SetFXNameText("name"," ");
} else {
SetFXNameText("name",nameText);
}
}
/* SetSkin() is a new method to deal with executing the texture-replacement
calls. If the value of the skin parameter is zero then texture replacement
will be turned off and the model will revert to the texture initially defined
in the mesh. Any other value will result in the corresponding texture from
the texture-group being assigned to the mesh. */
void SetSkin(int skin) {
if (skin == 0) SetFXTextureReplacement("masonry",null,0);
else SetFXTextureReplacement("masonry",skins,skin);
}
void ObjectHandler(Message msg) {
Vehicle vehicle = cast<Vehicle>msg.src;
if (!vehicle) return;
if (msg.minor == "InnerEnter") {
doorsOpen = true;
SetMeshAnimationState("default", true);
SetFXCoronaTexture("corona",GetCorona("default","corona"));
RingTheBell();
}
else if (msg.minor == "InnerLeave") {
doorsOpen = false;
SetMeshAnimationState("default", false);
SetFXCoronaTexture("corona",null);
}
}
/* GetNamedTagAsInt() allows us to initialise skin with a default value
if the tag doesn't already exist. GetNamedTag() retrieves a string value
and doesn't have the same capability, so we have to do a little additional
work to ensure that skinDescription is always initialised. If you look
back at the top of the file you will see that we assigned the value of
"None" to skinDescription when it was originally declared. We leave
that value alone unless soup has anything more interesting to tell us. */
public void SetProperties(Soup soup) {
inherited(soup);
roofVisible = soup.GetNamedTagAsBool ("roof",true);
SetMeshVisible("roof",roofVisible,1.0);
//nameText = soup.GetNamedTag("name");
nameText = GetLocalisedName();
SetNameText();
skin = soup.GetNamedTagAsInt("skin",0);
string temp = soup.GetNamedTag("skinDescription");
if (temp != "") skinDescription = temp;
SetSkin(skin);
}
public Soup GetProperties(void) {
Soup soup = inherited();
soup.SetNamedTag("roof",roofVisible);
soup.SetNamedTag("name",nameText);
soup.SetNamedTag("skin",skin);
soup.SetNamedTag("skinDescription",skinDescription);
return soup;
}
/* Additional code to present skinDescription in the Property Editor. */
public string GetDescriptionHTML(void) {
string roofStatus = "Show";
if (roofVisible) roofStatus = "Hide";
string html = inherited()
+ "<font size=5><br>"
+ "Roof: <a href=live://property/roof>" + roofStatus + "</a><br>"
+ "Name: <a href=live://property/name>" + nameText + "</a><br>"
+ "Name: <a href=live://property/skin>" + skinDescription + "</a><br>"
+ "</font>";
return html;
}
public string GetPropertyName(string pID) {
string result = inherited(pID);
if (pID == "name") result = "Enter Name Text";
else if (pID == "skin") result = "Select Masonry Texture";
return result;
}
/* Advise Trainz that the skin property is of type "list". */
public string GetPropertyType(string pID) {
string result = inherited(pID);
if (pID == "roof") result = "link";
else if (pID == "name") result = "string";
else if (pID == "skin") result = "list";
return result;
}
/* GetPropertyElementList() builds the list that we will be presenting to the
users to allow them to select a texture. The method returns an array, or
indexed list, of strings. It does this by loading the names of the skins
from the textures table in config.txt of the texture group. This table
lists all of the *.texture files which the asset contains. The method
then parses through the soup using a for loop and chops the ".texture"
off the end of the filename before adding it to our result array. */
public string[] GetPropertyElementList(string pID) {
string[] result = inherited(pID);
if (pID == "skin") {
result[result.size()] = "None";
Soup soup = skins.GetConfigSoup().GetNamedSoup("textures");
int n;
for (n = 1; n < soup.CountTags(); n++) {
result[result.size()] = Str.Tokens(soup.GetNamedTag(n),".")[0];
}
}
return result;
}
public string GetPropertyValue(string pID) {
string result = inherited(pID);
if (pID == "name") result = nameText;
return result;
}
/* You may have become used to simply adding new statements to the standard property
methods, but this is a bit of a special case. SetPropertyValue() is a new
function with the same name as a method which already exists but with a different
set of parameters. TrainzScript allows this duplication and it can be very useful,
it can also be a little confusing, so you need to watch out for it.
In this particular case we want the same method to deal with two values:
the skin index and the skin description. The first is a number representing the
position of the selection within the list, and the second is the string value of
the list item itself. Both are set in the same method before calling SetSkin()
which actually does the skin swapping. */
public void SetPropertyValue(string pID, string value, int index) {
if (pID == "skin") {
skin = index;
skinDescription = value;
SetSkin(skin);
}
else inherited(pID,value,index);
}
public void SetPropertyValue(string pID, string value) {
if (pID == "name") {
if (value == "") nameText = "None";
else nameText = value;
SetNameText();
}
else inherited(pID, value);
}
public void LinkPropertyValue(string pID) {
if (pID == "roof") {
roofVisible = !roofVisible;
SetMeshVisible("roof",roofVisible,1.0);
}
else inherited(pID);
}
};
In Part 2 I will discuss my test asset and initial experiments.