Prequel to Andi Smith's wiki tutorial shed house tutorial script

From TrainzOnline
(Difference between revisions)
Jump to: navigation, search
Line 244: Line 244:
 
     }  
 
     }  
  
 +
  /*
 +
    Step 9: GetDescriptionHTML() is where you define the HTML code that will be
 +
      loaded into the  Property Editor when you press [F3] in Surveyor, select
 +
      the [?] icon, and click on the Engine Shed. The HTML code supported is very
 +
      simple. You just need to provide the basics. The local variable roofStatus
 +
      is used to provide text to be used by the interface to show whether the
 +
      roof is to be hidden or displayed.
 +
      <a href=live://property/roof> is a special hyperlink format used by Trainz
 +
      to identify a link clicked within the Property Editor. The 'roof' part,
 +
      known as a propertyID, is used by other related functions to identify
 +
      values that the user wants to modify.
 +
    Step 10: Additional code to present fasciaText in the Property Editor.
 +
    Step 11: Additional code to present skinDescription in the Property Editor.
 +
      First we decide the browsertext to show. If config soup is cachhed without
 +
      error, we use the property-live-link. Otherwise if error code is 0, we tell
 +
      to try later because the caching isn't finished and otherwise we tell about
 +
      caching is failed.
 +
  */
 +
    public string GetDescriptionHTML(void) {
 +
      string roofKlickAction = "Show roof";
 +
      if (roofVisible) roofKlickAction = "Hide roof";
 +
      string selectedSkinText = "";
 +
      if (isConfigSoupCached)
 +
        selectedSkinText = "<a href=live://property/skin>" + skinDescription + "</a><br>";
 +
      else if (errCodeConfigCaching == 0)
 +
            selectedSkinText = "Caching yet activ. Try later again.";
 +
          else
 +
            selectedSkinText = "Caching failed with errorcode " + errCodeConfigCaching;
 +
      string html = inherited()
 +
      + "<font size=5>"
 +
      + "Roof visibility: <a href=live://property/roof>" + roofKlickAction + "</a><br>"
 +
      + "Gable labeltext: <a href=live://property/name>" + fasciaText + "</a><br>"
 +
      + "Selected skin: " + selectedSkinText
 +
      + "</font>";
 +
    return html;
 +
  }
  
  
----
 
----
 
----
 
----
 
----
 
  
  
 +
  /*
 +
    Step 10: When we edit fasciaText Trainz will provide a separate dialogue box
 +
      that we can type into. This method requests a caption for that dialogue. So
 +
      "GetPropertyName" is in the meaning of "get caption text of the input window".
 +
  */
 +
    public string GetPropertyName(string pID) {
 +
      string captiontext = inherited(pID);
 +
      if (pID == "name") captiontext = "Enter Textstring";
 +
      // Step 11: Additional code for skin list editor.
 +
      else
 +
        if (pID == "skin") captiontext = "Select Masonry Texture";
 +
      return captiontext;
 +
    }
  
 +
  /*
 +
    Step 10: When we edit fasciaText Trainz will provide a separate dialogue
 +
      box that we can type into. This method requests a additional tooltip
 +
      information for that dialogue. So "GetPropertyDescription" is in the
 +
      meaning of "get the text of the additional tooltip for the input window".
 +
  */
 +
    public string GetPropertyDescription(string pID) {
 +
      string description = inherited(pID);
 +
      if (pID == "name") description = "" + "The entered text string will be shown "
 +
                                          + "at the top of the door gable of the shed.";
 +
      // Step 11: Additional code for skin list editor tooltip.
 +
      if (pID == "skin") description = "" + "Choose the skin from list";
 +
      return description;
 +
    }
  
/*
+
  /*
  Step 9: GetDescriptionHTML() is where you define the HTML code that will be
+
    Step 9: The remaining methods are all standard routines called by the game to
    loaded into the  Property Editor when you press [F3] in Surveyor, select
+
      establish what needs to be done in response to a user click on any of the
    the [?] icon, and click on the Engine Shed. The HTML code supported is very
+
      links in the Property Editor. GetPropertyType() establishes whether the
    simple. You just need to provide the basics. The local variable roofStatus
+
      property, "roof" in our case, is to be treated as a number, as a string
    is used to provide text to be used by the interface to show whether the  
+
      value or is to be selected from a list.  Roof represents the simplest
    roof is to be hidden or displayed.
+
      property type, where merely clicking on the link is enough information for
    <a href=live://property/roof> is a special hyperlink format used by Trainz
+
      the game to do what needs to be done. We indicate this to Trainz by
    to identify a link clicked within the Property Editor. The 'roof' part,
+
      returning the string "link".  
    known as a propertyID, is used by other related functions to identify
+
  */
    values that the user wants to modify.
+
    public string GetPropertyType(string pID) {
  Step 10: Additional code to present fasciaText in the Property Editor.
+
      string result = inherited(pID);
  Step 11: Additional code to present skinDescription in the Property Editor.
+
      if (pID == "roof") result = "link";
    First we decide the browsertext to show. If config soup is cachhed without
+
      // Step 10: Advise Trainz that the name property is of type "string".
    error, we use the property-live-link. Otherwise if error code is 0, we tell
+
      if (pID == "name") result = "string";
    to try later because the caching isn't finished and otherwise we tell about
+
       // Step 11: Advise Trainz that the skin property is of type "list".
    caching is failed.  
+
       if (pID == "skin") result = "list";
*/
+
      return result;
  public string GetDescriptionHTML(void) {
+
    }
    string roofKlickAction = "Show roof";
+
    if (roofVisible) roofKlickAction = "Hide roof";
+
string selectedSkinText = "";
+
if (isConfigSoupCached)
+
  selectedSkinText = "<a href=live://property/skin>"  
+
    + skinDescription + "</a><br>";
+
else if (errCodeConfigCaching == 0)
+
  selectedSkinText = "Caching yet activ. Try later again.";
+
  else
+
  selectedSkinText = "Caching failed with errorcode "  
+
    + errCodeConfigCaching;
+
    string html = inherited()
+
       + "<font size=5>"
+
      + "Roof visibility: <a href=live://property/roof>"  
+
    + roofKlickAction + "</a><br>"
+
       + "Gable labeltext: <a href=live://property/name>"
+
    + fasciaText + "</a><br>"
+
      + "Selected skin: " + selectedSkinText
+
      + "</font>";
+
    return html;
+
  }
+
  
  
Line 308: Line 345:
  
  
/*
 
  Step 10: When we edit fasciaText Trainz will provide a separate dialogue box
 
    that we can type into. This method requests a caption for that dialogue. So
 
    "GetPropertyName" is in the meaning of "get caption text of the input
 
window".
 
*/
 
  public string GetPropertyName(string pID) {
 
    string captiontext = inherited(pID);
 
    if (pID == "name") captiontext = "Enter Textstring";
 
// Step 11: Additional code for skin list editor.
 
    else
 
if (pID == "skin") captiontext = "Select Masonry Texture";
 
    return captiontext;
 
  }
 
  
/*
 
  Step 10: When we edit fasciaText Trainz will provide a separate dialogue
 
    box that we can type into. This method requests a additional tooltip
 
    information for that dialogue. So "GetPropertyDescription" is in the
 
    meaning of "get the text of the additional tooltip for the input window".
 
*/
 
  public string GetPropertyDescription(string pID) {
 
    string description = inherited(pID);
 
    if (pID == "name") description = ""
 
                  + "The entered text string will be shown "
 
                  + "at the top of the door gable of the shed.";
 
// Step 11: Additional code for skin list editor tooltip.
 
    if (pID == "skin") description = ""
 
                  + "Choose the skin from list";
 
    return description;
 
  }
 
  
/*
 
  Step 9: The remaining methods are all standard routines called by the game to
 
    establish what needs to be done in response to a user click on any of the
 
    links in the Property Editor. GetPropertyType() establishes whether the
 
    property, "roof" in our case, is to be treated as a number, as a string
 
    value or is to be selected from a list.  Roof represents the simplest
 
    property type, where merely clicking on the link is enough information for
 
    the game to do what needs to be done. We indicate this to Trainz by
 
    returning the string "link".
 
*/
 
  public string GetPropertyType(string pID) {
 
    string result = inherited(pID);
 
    if (pID == "roof") result = "link";
 
    // Step 10: Advise Trainz that the name property is of type "string".
 
if (pID == "name") result = "string";
 
    // Step 11: Advise Trainz that the skin property is of type "list".
 
    if (pID == "skin") result = "list";
 
    return result;
 
  }
 
  
 
/*
 
/*

Revision as of 03:51, 14 August 2023

 include "Buildable.gs"
 /*
   Engineshed Tutorial Asset Prequel Blender
   -----------------------------------------
   Step  5: Setting up the asset
   Step  6: Automating door animations
   Step  7: Handling corona effects
   Step  8: Playing sound 
   Step  9: Hiding meshes 
   Step 10: Handling name effects 
   Step 11: Texture replacements
 */
 class Tutorial isclass Buildable {
 /*
   Step 6: We will need a variable to keep track of whether the shed doors are
     open or closed, declaring this in the body of the class and outside of any
     method definitions will make it available to any method in the class.
 */ 
   bool doorsOpen = false;
   // Step 9: Variable to knowing about if the roof is set visible or not  
   bool roofVisible = true;
 /*
   Step 10: It is legitimate to assign an initial value when a variable is
     declared. The value can be still altered by other code statements.
     Becaus the property browser has within links problems with empty ot blank
     strings (no clickable text) we workaround with a html enforced blank.
     And we are going to be calling a new method called SetFasciaText() in our
     Init() method. The method itself will be defined later on but the script
     will not compile unless it knows the parameters required by the method when
     it first finds a reference to it. To get around this we can predefine it in
     the body of the class. 
 */
   string fasciaText = " ";
   void SetFasciaText(void);
 /*
   Step 11: Global variables to hold the texture library, skin index and
     description.
 */
   Asset skins;    // Texture library asset object
   int skin;       // ID-number of the current skin
   string skinDescription   = "Original Texture";
   bool isConfigSoupCached  = false;   // Is the config soup cach ready?
   int errCodeConfigCaching = 0;       // Used to decide browser text variant
   thread void asyncConfigSoupCache(Asset asset);
 /*
   Step 5: Create the mandantory init-method
   Step 6: We are going to open and close the doors whenever a vehicle enters
     or leaves the area defined by our trigger. To do this we will be listening
     for Object,InnerEnter and Object, InnerLeave messages. We do this by adding
     a handler in Init().
 */  
   public void Init(void) {
     inherited();
     // Step 5: Switch corana light off because its initial on
     SetFXCoronaTexture("corona",null);
     // Step 10: Setting fasciaText to signboard at the shed gable
     SetFasciaText();
     // Step 6: Registering a trigger-handler
     AddHandler(me,"Object","","ObjectHandler");
     // Step 11: This call sets skins, to refer to the texture-group defined in
     //   our kuid-table. And then start the async caching of the config soup 
     //   with skin-list inside it. 
     skins = GetAsset().FindAsset("skinskuid");
     asyncConfigSoupCache(skins);
   }
 /*
   Step 11: The async thread method to cach the config-soup of the texture-
     library with error checking
 */
   thread void asyncConfigSoupCache(Asset asset) {
     AsyncQueryHelper queryHelper = asset.CacheConfigSoup();
     Sniff(queryHelper, "AsyncQuery", "", true);
     Message msg;
     wait() {
       on "AsyncQuery", "", msg: 
         if (msg.src == queryHelper) {
           break;
         }
         continue;
     }
     int errCode = queryHelper.GetQueryErrorCode();           
     if (queryHelper.IsErrorCode(errCode)) {
       Interface.Log("ConfigCaching - cannot load config - errcode = " + errCode);
       errCodeConfigCaching = errCode;
       return;
     }
     isConfigSoupCached = true;
   }
 /* 
   Step 7: GetCorona is a utility method that will read the texture-kuid tag 
     from the config.txt of our shed. When we turned the corona off in Init()
     the reference to the KUID of the corona was lost and there is no easy way
     to reset it. Thismethod retrieves the value and returns it as a corona 
     asset. Code which reads datafrom a Soup, which is the Trainz word for
     database, is often very convoluted and difficult to follow. I have split
     the code into different calls here, and made it a separate method in order
     to keep it readable, but it could have been writtenas one long statement.
 */ 
   // TBD: Question to proof: Is it possible to store it in a variable?
   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);
   }
 /*
   Step 7: We can use the same mechanism to control the coronas that we are
     using to open and close the shed doors, we just need to add the appropriate
     statements.
   Step 8: This is the bell ringing method, the keyword thread signifies that
     it is to run independently of any other code. You will remember our global
     variable, doorsOpen which is set to true or false in the ObjectHandler
     method. RingTheBell uses a while loop to monitor this variable and will
     keep running until doorsOpen becomes false. World.Play2DSound() plays a
     non-positional sound, that is it can be heard anywhere on the route. I've
     added a call to Sleep after the sound is played, enough time to allow the
     sound to play, plus a short pause. On the next loop the method will check
     to see if the doors are still open and run again if so. Once they have
     closed the while loop and the method will end. All of this runs
     independently of other parts of the class code.
 */
   thread void RingTheBell(void) {
     while (doorsOpen) {
       World.Play2DSound(GetAsset(),"bell.wav");
       Sleep(1.5);
     }
   }
 /* 
   Step 10: This is the actual declaration of SetFasciaText(). We are going to
     use this method to work around the null string and blank parameters problem
     with the browser link. Since this hack will be needed several times we have
     'centralised' it here.
 */
   void SetFasciaText(void) {
     if (fasciaText == "" or fasciaText == " ") {
       SetFXNameText("name"," ");   // Show a blank signboard
     } else {
       SetFXNameText("name",fasciaText);
     }
   }
 /*
   Step 11: 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);
   }
 /* 
   Step 6: Object handler will 'wake up' whenever we receive an Object message.
     It is good  practice to check that the message is coming from an expected
     source. This is done by converting or 'casting' the source of the message 
     to a vehicle.If the cast does not work the variable vehicle will be null
     and the handler is exited immediately. The remainder of the method checks
     whether the vehicle is entering or leaving and runs the animations as
     appropriate. SetFXAnimationState() runs the animation attached to the
     named effect container from its current position to the beginning or end,
     depending on the value of the second parameter.
   Step 8: When the doors are opened we call the thread RingTheBell to start the
     bell ringing. There is no need to stop it, this will happen automatically. 
 */
   void ObjectHandler(Message msg) {
     // Step 6: Casting let us know about that no other kind of
     // object has triggered the trigger, only a vehicle.
     Vehicle vehicle = cast<Vehicle>msg.src;
     if (!vehicle) return;
     if (msg.minor == "InnerEnter") {
       doorsOpen = true;
       SetFXAnimationState("doors", true);
       // Step 7: Switch the light over the doors on 
       SetFXCoronaTexture("corona",GetCorona("default-lod0","corona"));
       // Step 8: Ring the bell
       RingTheBell();
     }
     else if (msg.minor == "InnerLeave") {
       doorsOpen = false;
       SetFXAnimationState("doors", false);
       // Step 7: Switch the light over the doors off
       SetFXCoronaTexture("corona",null);
     }
   }
 /*
   Step 9: SetProperties() is another method which is called by the game engine.
     Its purpose is to read the session files and to provide you with the
     opportunity to retrieve any data which has been saved there in previous
     runs. The method is also called when the Property Editor is closed, to save
     any changes which have been made. The Undo system also uses this method. 
     We are using it here to restore any value for the roof visibility which has
     previously been saved by the game. There will be nothing there of course,
     when the asset is first placed.
   Step 10: First time call of GetNamedTag there is a "" string result, if the
     tag doesn't exist. So we have to test it to not loose the fasciaText value. 
   Step 11: 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 "Original
     Texture" 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-lod0",roofVisible,1.0);
     // Step 10: Additional code to retrieve saved values for fasciaText.
     string text = soup.GetNamedTag("name");
     if (text!="") fasciaText=text;
     SetFasciaText();
     // Step 11: Additional code
     skin = soup.GetNamedTagAsInt("skin",0);
     string temp = soup.GetNamedTag("skinDescription");
     if (temp != "") skinDescription = temp;
     SetSkin(skin);
   }
 /*
   Step 9: GetProperties() is again called by the game engine, this time it's
     used to take any changes made by the user and to save them to the session files. 
 */
   public Soup GetProperties(void) {
      Soup soup = inherited();
     soup.SetNamedTag("roof",roofVisible);
     // Step 10: Additional code to save the present value of fasciaText.
     soup.SetNamedTag("name",fasciaText);
     // Step 11: Additional code
     soup.SetNamedTag("skin",skin);
     soup.SetNamedTag("skinDescription",skinDescription);
     return soup;
   } 
 /*
   Step 9: GetDescriptionHTML() is where you define the HTML code that will be
     loaded into the  Property Editor when you press [F3] in Surveyor, select
     the [?] icon, and click on the Engine Shed. The HTML code supported is very
     simple. You just need to provide the basics. The local variable roofStatus
     is used to provide text to be used by the interface to show whether the 
     roof is to be hidden or displayed. 
     <a href=live://property/roof> is a special hyperlink format used by Trainz
     to identify a link clicked within the Property Editor. The 'roof' part,
     known as a propertyID, is used by other related functions to identify
     values that the user wants to modify.
   Step 10: Additional code to present fasciaText in the Property Editor.
   Step 11: Additional code to present skinDescription in the Property Editor.
     First we decide the browsertext to show. If config soup is cachhed without
     error, we use the property-live-link. Otherwise if error code is 0, we tell
     to try later because the caching isn't finished and otherwise we tell about
     caching is failed. 
 */
   public string GetDescriptionHTML(void) {
     string roofKlickAction = "Show roof";
     if (roofVisible) roofKlickAction = "Hide roof";
     string selectedSkinText = "";
     if (isConfigSoupCached)
       selectedSkinText = "<a href=live://property/skin>" + skinDescription + "</a><br>";
     else if (errCodeConfigCaching == 0)
            selectedSkinText = "Caching yet activ. Try later again.";
          else 
            selectedSkinText = "Caching failed with errorcode " + errCodeConfigCaching;
     string html = inherited()
     + "<font size=5>"
     + "Roof visibility: <a href=live://property/roof>" + roofKlickAction + "</a><br>"
     + "Gable labeltext: <a href=live://property/name>" + fasciaText + "</a><br>"
     + "Selected skin: " + selectedSkinText
     + "</font>";
   return html;
 }



 /*
   Step 10: When we edit fasciaText Trainz will provide a separate dialogue box 
     that we can type into. This method requests a caption for that dialogue. So 
     "GetPropertyName" is in the meaning of "get caption text of the input window".
 */
   public string GetPropertyName(string pID) {
     string captiontext = inherited(pID);
     if (pID == "name") captiontext = "Enter Textstring";
     // Step 11: Additional code for skin list editor.
     else 
       if (pID == "skin") captiontext = "Select Masonry Texture";
     return captiontext;
   }
 /*
   Step 10: When we edit fasciaText Trainz will provide a separate dialogue 
     box that we can type into. This method requests a additional tooltip
     information for that dialogue. So "GetPropertyDescription" is in the
     meaning of "get the text of the additional tooltip for the input window".
 */
   public string GetPropertyDescription(string pID) {
     string description = inherited(pID);
     if (pID == "name") description = "" + "The entered text string will be shown "
                                         + "at the top of the door gable of the shed.";
     // Step 11: Additional code for skin list editor tooltip.
     if (pID == "skin") description = "" + "Choose the skin from list";
     return description;
   }
 /*
   Step 9: The remaining methods are all standard routines called by the game to
     establish what needs to be done in response to a user click on any of the
     links in the Property Editor. GetPropertyType() establishes whether the
     property, "roof" in our case, is to be treated as a number, as a string
     value or is to be selected from a list.  Roof represents the simplest
     property type, where merely clicking on the link is enough information for
     the game to do what needs to be done. We indicate this to Trainz by
     returning the string "link". 
 */
   public string GetPropertyType(string pID) {
     string result = inherited(pID);
     if (pID == "roof") result = "link";
     // Step 10: Advise Trainz that the name property is of type "string".
     if (pID == "name") result = "string";
     // Step 11: Advise Trainz that the skin property is of type "list".
     if (pID == "skin") result = "list";
     return result;
   }










/*

 Step 11: 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()] = "Original Texture";
     // Step 11: Because GetConfigSoup is obsolete this is exchanged with a 

// async caching of the config soup. // Soup soup = skins.GetConfigSoup().GetNamedSoup("textures"); Soup soup = skins.GetConfigSoupCached().GetNamedSoup("textures");

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

/*

 Step 11: 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);
 }

/*

 Step 10: GetPropertyValue() enters the current value of fasciaText into a
   dialogue to allow it to be edited. 
  • /
 public string GetPropertyValue(string pID) {
   string result = inherited(pID);
   if (pID == "name") result = fasciaText;
   return result;
 }

/*

 Step 10: SetPropertyValue() takes our edited value and assigns it to our
   fasciaText variable. This will only happen if we actually change the value
   in the TRS dialogue. Otherwise the original value will not be altered. 
  • /
 public void SetPropertyValue(string pID, string value) {
   if (pID == "name") {

// TBD: Empty multi blank string

     // TBD: Special characters and HTML	  
     if (value == "" or value == " ") fasciaText = " ";
     else fasciaText = value;
     SetFasciaText();
   }
   else inherited(pID, value);
 }

/*

 Step 9: LinkPropertyValue() is where we tell the game what is to be done
   when properties which have been defined as "link" are pressed. In our
   case we take the boolean value roofVisible and make it the opposite of
   itself, toggling its value. (The symbol ! means not). Having done this we
   can use the new value of roofVisible as a parameter to SetMesh() which
   hides or shows the submesh. The final parameter of SetMeshVisible()
   specifies the time to fade the mesh in or out. 
  • /
 public void LinkPropertyValue(string pID) {
   if (pID == "roof") {
     roofVisible = !roofVisible;
     SetMeshVisible("roof-lod0",roofVisible,1.0);
   }
   else inherited(pID);
 }
 

};


Back: Prequel_Blender_to_Trainz#Intro


Personal tools