Texture Replacement.

From TrainzOnline
Jump to: navigation, search

Since this tutorial is concerned with script and not with the creation of texture-group assets we will be using a ready made texture library. If you are using my gmax source the mapping is already suitable for use with this library, otherwise you may need to make some adjustments. There are one or two things you will need to do in any event to set up the Engine Shed for texture replacement.

  • If you don't already have it you will need to download a copy of AJS Masonry Textures <kuid:122285:3599> from the DLS, this is about 1Mb.
  • You will need to open the Engine Shed config.txt file and add a kuid-table including a reference to this asset:
kuid-table {
   skins   <kuid:122285:3599>
}

The texture group contains a library of masonry textures which you can use to change the appearance of the shed walls. As before this will be controlled by the Property Editor, but this time we want to be able to select the texture required from a list rather than having to type its name. This will require modifications to the code and some additional Property Handler methods.

include "Buildable.gs"

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");
      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);
   }

};

You may have found these statements in GetPropertyElementList() a little bewildering:

for(n = 1; n < soup.CountTags(); n++) {
   result[result.size()] = Str.Tokens(soup.GetNamedTag(n),".")[0];
}

for() defines a loop which is executed a set number of times and requires three statements as parameters. Since these are statements and not formal parameters they are terminated with ; rather than ,

  • n is an integer counter variable, and n = 1 is a statement defining the initial value of the counter
  • The second statement sets out a conditional test. While this test is true the loop will continue to run. In our case the test is the number of tag values in the soup.
  • The third statement n++ defines what happens to n at the end of each loop, n++ means add 1 to the value of n.

result[result.size()] = instructs the script to add a new value to the end of an array.

  • Array indices start at zero, so on the first run with an empty array this would evaluate to result[0] =

soup.GetNamedTag(n) returns the value of the nth tag in the database. Although n is a number TrainzScript is usually quite accommodating at converting it to a string where this makes sense. Let's assume that this has returned "Brick, Dark Stock.texture".

Str.Tokens("Brick, Dark Stock.texture",".")[0]; Str.Tokens() is a method which splits a string up into separate 'words' based on the value of the second parameter, in this case a full stop. The method returns an array of strings. In our case the first element of this array would be "Brick, Dark Stock" and the second would be "texture". The [0] at the end of the statement is an array index notation which signifies that we are interested in the first element of the array. So the whole expression serves to return the phrase "Brick, Dark Stock".

Back to Getting Started in TrainzScript

Personal tools