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

From TrainzOnline
Jump to: navigation, search

Back to: Prequel_Blender_to_Trainz#Intro


 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) {
     string text = Str.CloneString(fasciaText);
     Str.TrimLeft(text,"");
     if (text == "")
       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("xxx", false) set the attached animation 
     effect xxx inactiv (restet to frame 0) and SetFXAnimationState("xxx", true)
     set the animation effect xxx activ and runs the attached animation.
   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("closedoors", false);
       SetFXAnimationState("opendoors", true);
       // Step 7: Switch the light over the doors on 
       SetFXCoronaTexture("corona",GetCorona("shed-lod0","corona"));
     // Step 8: Ring the bell
     RingTheBell();
     }
     else if (msg.minor == "InnerLeave") {
       doorsOpen = false;
       SetFXAnimationState("opendoors", false);
       SetFXAnimationState("closedoors", true);
     // 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");
     Str.TrimLeft(text,"");	  
     if (text!="") fasciaText=Str.CloneString(text);

else fasciaText=" ";

     SetFasciaText();
     // Step 11: Additional code
     skin = soup.GetNamedTagAsInt("skin",0);
     string temp = soup.GetNamedTag("skinDescription");
     if (temp != "") skinDescription = Str.CloneString(temp);

else skinDescription = "Original Texture";

     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;
     // Handle special characters in fasciaText with Quote method and blank  
     string showText = "";
     if (fasciaText == " ") showText = " ";
     else showText = BrowserInterface.Quote(fasciaText);
     string html = inherited()
       + "<font size=5>"
       + "Roof visibility: <a href=live://property/roof>" + roofKlickAction + "</a><br>"
       + "Gable labeltext: <a href=live://property/name>" + showText + "</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") {
       // 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 = 0; n < soup.CountTags(); n++) {
         result[result.size()] = Str.Tokens(soup.GetNamedTag(n),".")[0];
       }
       result[result.size()] = "Original Texture";
     }
     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 = Str.CloneString(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") {
       // Proof if value is white space string
       string temp = Str.CloneString(value);
       Str.TrimLeft(temp,"");
       if (temp == "") fasciaText = " ";
       else fasciaText = Str.CloneString(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