Dispatcher

From TrainzOnline
Jump to: navigation, search

Contents

Dispatcher and library

The dispatcher asset works as central unique instance for all aCTS objects. It collects the posted object data as soups, registers all that objects and reposts informations to the objects. From the sight of assets it is a script library.

 TBD: Update text
 TBD: Repost or update additional informations after reposting the registered objectsoup.
 * CONTCRANE: Repost a goid list of reachable stacks. The contcrane itself informs the stacks about it's object data to update the maximum count of stackable containers.
 * CONTSTACK: ???
 * CONTAINER: If a container has no stack place, randomly attach him to a free stack place and repost it. In case there is no one, repost an empty stack goid with place zero. TBD: What happens if the stack it attached to does not exist? Possibly handled as a free container?

Asset files pre version 0.1

The files compile errorless and the messaging runs well. No more functionality implemented and tested in this pre version.

config.txt

 kuid               <kuid2:215489:110000:1>
 username           "aCTS DISPATCHER Library V1"
 kind               "Library"
 category-class     "YL"
 trainz-build       4.6
 
 description        "aCTS DISPATCHER Library V1"
 
 script             "acts_dispatcher.gs"
 class              "acts_dispatcher"
 
 thumbnails
 {
   0
   {
     image          "acts_dispatcher_thumb.jpg"
     width          240
     height         180
   }
 } 

acts_dispatcher.gs

 include "acts_global_values.gs"
 include "acts_tools_script.gs"
 
 //-----------------------------------------------------------------------------
 // aCTS Dispatcher V0.1 Central Script - acts_dispatcher
 //-----------------------------------------------------------------------------
 // X acts_dispatcher             <-- acts_dispatcher_script
 // X acts_dispatcher_script      <-- acts_dispatcher_globals
 // X acts_dispatcher_globals     <-- acts_global_values
 //   acts_global_values          <-- MapObject
 //-----------------------------------------------------------------------------
 
 //-----------------------------------------------------------------------------
 class acts_dispatcher_globals isclass acts_global_values
 {
   //---------------------------------------------------------------------------
   // Store from dispatcher received object registration values to the 
   // respective dispatchers sub soup. The sub soups register ids run as int  
   // from 0 ... The received id -1 tells, that the object isn't yet
   // registered.
   //---------------------------------------------------------------------------
   // Because the register id number list gets gaps while unregistering it will
   // be nessecary to have some gap filling methods additionally. See
   // AppendInsertUpdateObjectDataSoups and FindObjectSoupEntry methods.
   //---------------------------------------------------------------------------
   // AssetInstanceSoup {
   //   ObjectDataSoup { ... },    Only container, contstack, contcrane
   //   LocationSoup { ... },      Only container, contstack, contcrane
   //   OrientationSoup { ... },   Only container, contstack, contcrane
   //   SkinDataSoup { ... },      Only container
   //   ContentDataSoup { ... },   Only container
   // X DispatcherDataSoup { ... } Only dispatcher
   // X ContainerSoups  { ... }    Only dispatcher
   // X ContstackSoups  { ... }    Only dispatcher
   // X ContcraneSoups  { ... }    Only dispatcher
   // }
   //---------------------------------------------------------------------------
   Soup ContainerSoups = Constructors.NewSoup();
   Soup ContstackSoups = Constructors.NewSoup();
   Soup ContcraneSoups = Constructors.NewSoup();
   //---------------------------------------------------------------------------
   Soup DispatcherDataSoup = Constructors.NewSoup();
   //---------------------------------------------------------------------------
   // ObjectDataSoup received from and posted to objects.
   //---------------------------------------------------------------------------
   // ObjectDataSoup { goid GameObjectID, type string, dispID int,
   //                  length int, height int,
   //                  stack goid, place int,           Only container
   //                  active bool, action string },
   // activ: true means the object sended a register message while session run.
   //        All objects were set false while initialising dispatcher and if
   //        they stay false their entries will be deleted next initialising.
   // actions: ["APPENDED", "INSERTED", "UPDATED"];
   //---------------------------------------------------------------------------
   GameObjectID dispgoid = me.GetGameObjectID();
   string objecttype;
   GameObjectID objectgoid;
   //---------------------------------------------------------------------------
 };
 
 class acts_dispatcher_script isclass acts_dispatcher_globals
 {
   //---------------------------------------------------------------------------
   public void SetProperties(Soup soup)   // Callback method after Init method
   {
     inherited(soup);
     //-------------------------------------------------------------------------
     DispatcherDataSoup.Copy(soup.GetNamedSoup("DispatcherData"));
     ContainerSoups.Copy(soup.GetNamedSoup("ContainerSoups"));
     ContstackSoups.Copy(soup.GetNamedSoup("ContstackSoups"));
     ContcraneSoups.Copy(soup.GetNamedSoup("ContcraneSoups"));
     //-------------------------------------------------------------------------
     isSetPropertiesMethodFinnished = true;
     PostMessage(me,"OBJECTINIT","SETPROPS_FINNISH",0);
     return;
   }
   //---------------------------------------------------------------------------
   public Soup GetProperties(void)   // Callback method before closing session 
   {
     Soup soup = inherited();
     //-------------------------------------------------------------------------
     DispatcherDataSoup.SetNamedTag("firstrun",false);
     //-------------------------------------------------------------------------
     soup.SetNamedSoup("DispatcherData",DispatcherDataSoup);
     soup.SetNamedSoup("ContainerSoups",ContainerSoups);
     soup.SetNamedSoup("ContstackSoups",ContstackSoups);
     soup.SetNamedSoup("ContcraneSoups",ContcraneSoups);
     //-------------------------------------------------------------------------
     return soup;
   } 
   //---------------------------------------------------------------------------
   public void WorldInitHandler(Message msg)
   {
     if (acts_debug) Interface.Log(Interface.GetTimeStamp()
             + " [" + dispgoid.SerialiseToString() + "]"
             + " DISPATCHER GOT WORLDINIT MESSAGE ["
             + msg.major+"] ["+msg.minor+"]");
     //-------------------------------------------------------------------------
     // If one of the two methods (Init or SetProperties) isn't finished yet, 
     // post "World","ModuleInit" message a little bit later again.
     // So it is guaranteed that the Init and SetProperties methods are finished
     // as well as the "World","ModuleInit" message tells to be save using the 
     // World.GetCurrentModule method.
     //-------------------------------------------------------------------------
     if (!(isSetPropertiesMethodFinnished and isInitMethodFinnished))
     {
        //----------------------------------------------------------------------
        // While first run and before first session save for libraries the 
        // SetProperties methon will not run, whle no soup stored. If this case
        // isn't handeled, the repost of "World","ModuleInit" runs infinitly.
        //----------------------------------------------------------------------
        if (!DispatcherDataSoup.GetNamedTagAsBool("firstrun")) 
           PostMessage(me, "World", "ModuleInit", 0.1);
     }
     //-------------------------------------------------------------------------
     isWorldInitFinnished = true;
     PostMessage(me, "OBJECTINIT", "WORLD_INIT_FINNISHED", 0.0);
     //-------------------------------------------------------------------------
     return;
   }
   //---------------------------------------------------------------------------
   public void ObjectInitHandler(Message msg)
   {
     //-------------------------------------------------------------------------
     // Until the Trainz core send the "World","ModuleInit" message it is not
     // guarented to decide with the World.GetCurrentModule method to be in the 
     // returned module! 
     //-------------------------------------------------------------------------
     if (acts_debug) Interface.Log(Interface.GetTimeStamp()
             + " [" + dispgoid.SerialiseToString() + "]"
             + " DISPATCHER GOT MESSAGE ["+msg.major+"] ["+msg.minor+"]");
     //-------------------------------------------------------------------------*/
     if (msg.dst==me and msg.major=="OBJECTINIT")
     {
       //-----------------------------------------------------------------------
       if (msg.minor=="INIT_FINNISHED")
       {
         //---------------------------------------------------------------------
         // Not used yet
         //---------------------------------------------------------------------
       }
       //-----------------------------------------------------------------------
       if (msg.minor=="SETPROPS_FINNISHED")
       {
         //---------------------------------------------------------------------
         // If DispatcherDataSoup is empty, this is the first call unsaved call
         //---------------------------------------------------------------------
         if (DispatcherDataSoup.CountTags()==0)
            DispatcherDataSoup.SetNamedTag("firstrun",true);
       }
       //-----------------------------------------------------------------------
       if (msg.minor=="WORLD_INIT_FINNISHED")
       {
         //---------------------------------------------------------------------
         // Do no module only, driver module only or surveyor module only
         // related initiations
         //---------------------------------------------------------------------
         if (World.GetCurrentModule() == World.DRIVER_MODULE)
         {
           //-------------------------------------------------------------------
           // Not used yet
           //-------------------------------------------------------------------
         }
         //---------------------------------------------------------------------
         if (World.GetCurrentModule() == World.SURVEYOR_MODULE)
         {
           //-------------------------------------------------------------------
           // Not used yet
           //-------------------------------------------------------------------
         }
         //---------------------------------------------------------------------
         if (World.GetCurrentModule() == World.NO_MODULE)
         {
           //-------------------------------------------------------------------
           // Not used yet
           //-------------------------------------------------------------------
         }
       }
     }
   }
   //---------------------------------------------------------------------------
   // Search the objects soup for next free entry gap. Parameter dsp is the
   // object's type dispatcher sub soup (ContainerSoups, ContstackSoups or
   // ContcraneSoups). Method returns a soup.
   // soup { entry int, action string, active bool }
   //---------------------------------------------------------------------------
   Soup FindObjectSoupEntry(GameObjectID goid, Soup dsp)
   {
     int entry;
     string action;
     Soup rsp = Constructors.NewSoup();
     rsp.SetNamedTag("active",true);
     int i;
     for (i=0; i<dsp.CountTags(); i++)
     {
       entry = Str.ToInt(dsp.GetIndexedTagName(i));
       Soup sp = dsp.GetNamedSoup(entry);
       if (sp.GetNamedTagAsGameObjectID("goid")==goid)   // TBD: goid compar ison
       {
         rsp.SetNamedTag("entry",entry);
         rsp.SetNamedTag("action","UPDATED");
         return rsp;
       }
     }
     entry = actsTools.GetObjectSoupGap(dsp);
     if (entry==dsp.CountTags()) rsp.SetNamedTag("action","APPENDED");
     else rsp.SetNamedTag("action","INSERTED");
     rsp.SetNamedTag("entry",entry);
     return rsp;
   }
   //---------------------------------------------------------------------------
   // Search the goid and update, insert or append the soup to register
   // Parameter osp is the posted ObjectDataSoup. The other three are the
   // dispatcher's object soups subsoups.
   //---------------------------------------------------------------------------
   void AppendInsertUpdateObjectDataSoups(Soup osp,
                     Soup ContSps, Soup StackSps, Soup CraneSps)
   { 
     //-------------------------------------------------------------------------
     string type = osp.GetNamedTag("type");
     GameObjectID objgoid = osp.GetNamedTagAsGameObjectID("type");
     Soup objsps = Constructors.NewSoup();
     if (type == "CONTAINER") objsps = ContSps;
     if (type == "CONTSTACK") objsps = StackSps;
     if (type == "CONTCRANE") objsps = CraneSps;
     Soup entry = FindObjectSoupEntry(objgoid, objsps);
     // entry { entry int, active bool, action string }
     // actions = ["APPENDED", "INSERTED", "UPDATED"];
     osp.SetNamedTag("active", true);
     osp.SetNamedTag("action", entry.GetNamedTag("action"));
     objsps.SetNamedSoup(entry.GetNamedTagAsInt("entry"), osp);
     //-------------------------------------------------------------------------
     // Post retour that register is updated
     //-------------------------------------------------------------------------
     GameObject obj = Router.GetGameObject(objgoid);
     PostMessage(obj, "DISPATCHER",
                      "OBJECTSOUP_" + entry.GetNamedTag("action"), osp, 0.0);
     //-------------------------------------------------------------------------
     if (acts_debug) Interface.Log(Interface.GetTimeStamp()
             + " [" + dispgoid.SerialiseToString() + "]"
             + " DISPATCHER OBJECTSOUP " + entry.GetNamedTag("action") 
             + " " + type + " GOID [" + objgoid.SerialiseToString() + "]");
     if (acts_debug) Interface.Log(Interface.GetTimeStamp()
             + " CONTENT OF " + entry.GetNamedTag("action") + " SOUP FROM GOID"
             + " [" + objgoid.SerialiseToString() + "]");
     if (acts_debug) actsTools.ShowSoup(osp,"OSP=>");   
     //-------------------------------------------------------------------------*/
     return;
   }
   //---------------------------------------------------------------------------
   public void RegisterHandler(Message msg)
   {
     if (msg.dst==me and msg.major=="REGISTER")
     {
       //-----------------------------------------------------------------------
       if (acts_debug) Interface.Log(Interface.GetTimeStamp()
               + " ["+dispgoid.SerialiseToString()+"]"
               + " DISPATCHER GOT MESSAGE FROM OBJECT ["
               + (cast<GameObject>msg.src).GetGameObjectID().SerialiseToString()
               + "] [" + msg.major + "] [" + msg.minor + "]");
       //-----------------------------------------------------------------------*/
       if (msg.minor=="REGISTER_ME")
       {
         //---------------------------------------------------------------------
         // Update, insert or append the ObjectDataSoup
         //---------------------------------------------------------------------
         Soup RegDataSoup = Constructors.NewSoup();
         RegDataSoup.Copy(cast<Soup>msg.paramSoup);
         //---------------------------------------------------------------------
         if (acts_debug) Interface.Log(Interface.GetTimeStamp()
                       + " SOUP CONTENT OF 'DataSoup To Register' GOID"
                       + " [" + dispgoid.SerialiseToString() + "]");
         if (acts_debug) actsTools.ShowSoup(RegDataSoup,"=>");
         //---------------------------------------------------------------------
         AppendInsertUpdateObjectDataSoups(RegDataSoup, 
                           ContainerSoups, ContstackSoups, ContcraneSoups);
         //---------------------------------------------------------------------
         // TBD: Repost or update additional informations
         //   * CONTCRANE: Repost a goid list of reachable stacks. The contcrane
         //       itself informs the stacks about it's object data to update the
         //       maximum count of stackable containers.
         //   * CONTSTACK: ???
         //   * CONTAINER: If a container has no stack place, randomly attach
         //       him to a free stack place and repost it. In case there is no
         //       one, repost an empty stack goid with place zero.
         //       TBD: What happens if the stack it attached to does not exist?
         //            Possibly handled as a free container?       
         //---------------------------------------------------------------------
       }
       //-----------------------------------------------------------------------
     }
     return;
   }
 };
 
 class acts_dispatcher isclass acts_dispatcher_script
 {
   public void Init()
   {
     inherited();
     //-------------------------------------------------------------------------
     // Make shure that the right module is set for World.GetCurrentModle()
     // (see world.gs lines 939ff.) to start driver only or surveyor only
     // related initiations and tell me, if my Init and SetProperties methods
     // are finished
     //-------------------------------------------------------------------------
     AddHandler(me, "World", "ModuleInit", "WorldInitHandler");
     AddHandler(me, "OBJECTINIT", "", "ObjectInitHandler");
     AddHandler(me, "REGISTER",   "", "RegisterHandler");
     //-------------------------------------------------------------------------
     // Set asset special initial values
     //-------------------------------------------------------------------------
     objecttype = "DISPATCHER";
     //-------------------------------------------------------------------------
     isInitMethodFinnished = true;
     PostMessage(me,"OBJECTINIT","INIT_FINNISHED",0.0);   // Init method finnished
     return;
   }
 };

acts_global_values.gs

 include "mapobject.gs"
 
 //-----------------------------------------------------------------------------
 // Common aCTS values V0.1 - Dispatcher, Container, Stack, Crane Tools Script
 //-----------------------------------------------------------------------------
 
 class acts_global_values isclass MapObject
 {
   //---------------------------------------------------------------------------
   // Global variables
   //---------------------------------------------------------------------------
   bool acts_debug = true;
   //---------------------------------------------------------------------------
   float baseboardwidth = 720;
   //---------------------------------------------------------------------------
   // Technical values from normatives
   // --------------------------------------------------------------------------
   int container_length_10ft =  2991;   // in [mm]
   int container_length_20ft =  6058;   // in [mm]
   int container_length_40ft = 12192;   // in [mm]
   int container_length_45ft = 13716;   // in [mm]
   int container_length_53ft = 16154;   // in [mm]
   // --------------------------------------------------------------------------
   int container_width   = 2438;   // in [mm]
   int container_height  = 2591;   // in [mm]
   int container_deltahc =  305;   // in [mm]
   // --------------------------------------------------------------------------
   bool isWorldInitFinnished = false;
   bool isInitMethodFinnished = false;
   bool isSetPropertiesMethodFinnished = false;
   //---------------------------------------------------------------------------
 };

acts_tools_script.gs.gs

 include "acts_global_values.gs"
 
 //-----------------------------------------------------------------------------
 // Common aCTS methods V0.1 - Dispatcher, Container, Stack, Crane Tools Script
 //-----------------------------------------------------------------------------
 
 final static class actsTools isclass acts_global_values
 {
   //---------------------------------------------------------------------------
   // Log soup content of a soup of soups only and without subsoups
   //---------------------------------------------------------------------------
   public void ShowSoup(Soup sp, string ind)
   {
     if (sp.CountTags()!=0)
     {
       int i;
       for (i=0; i<sp.CountTags(); i++)
         if (acts_debug) Interface.Log(ind + sp.GetIndexedTagName(i)
                 + " ["+sp.GetNamedTag(sp.GetIndexedTagName(i))+"]");
     }
     return;
   }
   public void ShowSoups(Soup soup, string indent)
   {
     string ind = "="+indent;
     int sc = soup.CountTags();
     int i;
     for (i=0; i<sc; i++)
     {
       if (acts_debug) Interface.Log(indent + i + ".SubSoup");
       actsTools.ShowSoup(soup.GetNamedSoup(i),ind);
     }
     return;
   }
   //---------------------------------------------------------------------------
   // Set all ObjectSoups active false and get the last insert gap
   // TBD: Starting with a sorted soup 
   //---------------------------------------------------------------------------
   public void ResetObjectSoups(Soup soup)
   {
     int i;
     int gap = soup.CountTags();;
     for (i=0; i<soup.CountTags(); i++)
     {
       int tn = Str.ToInt(soup.GetIndexedTagName(i));
       Soup sp = soup.GetNamedSoup(tn);
       if (sp.GetNamedTagAsBool("active"))
       {
         sp.SetNamedTag("active",false);
         soup.SetNamedSoup(tn,sp);
       } else soup.RemoveNamedTag(tn); 
     }
     return;
   }
   //---------------------------------------------------------------------------
   // Return the first indexed gap in existing objectsoups tagnames
   //---------------------------------------------------------------------------
   public int GetObjectSoupGap(Soup soup)
   {
     int i;
     for (i=0; i<soup.CountTags(); i++)
       if (soup.GetIndexForNamedTag(i)==-1) return i;
     return soup.CountTags();
   }
   //---------------------------------------------------------------------------
   // Generate soup representation from WorldCoordinate
   //---------------------------------------------------------------------------
   public Soup WCoordToSoup(WorldCoordinate pos)
   {
     Soup sp = Constructors.NewSoup();
     sp.SetNamedTag("bbx",pos.baseboardX);
     sp.SetNamedTag("bby",pos.baseboardY);
     sp.SetNamedTag("px",pos.x);
     sp.SetNamedTag("py",pos.y);
     sp.SetNamedTag("pz",pos.z);
     return sp;
   }
   //---------------------------------------------------------------------------
   // Generate WorldCoordinate from its soup representation
   //---------------------------------------------------------------------------
   public WorldCoordinate WCoordFromSoup(Soup sp)
   {
     WorldCoordinate WC;
     WC.baseboardX = sp.GetNamedTagAsInt("bbx");
     WC.baseboardY = sp.GetNamedTagAsInt("bby");
     WC.x = sp.GetNamedTagAsFloat("px");
     WC.y = sp.GetNamedTagAsFloat("py");
     WC.z = sp.GetNamedTagAsFloat("pz");
     return WC;
   }
   //---------------------------------------------------------------------------
   // Normalize the WC to baseboard width
   //---------------------------------------------------------------------------
   public WorldCoordinate WCoordNormalize(WorldCoordinate WC)
   {
     WorldCoordinate nWC;
     //-------------------------------------------------------------------------
     int bx = WC.x * 1000.0 / baseboardwidth;   // Basebord delta
     if (WC.x < 0.0) bx--;
     nWC.baseboardX = WC.baseboardX + bx;
     nWC.x = WC.x - bx * baseboardwidth;
     //-------------------------------------------------------------------------
     int by = WC.y * 1000.0 / baseboardwidth;   // Basebord delta
     if (WC.y < 0.0) by--;
     nWC.baseboardY = WC.baseboardX + by;
     nWC.y = WC.y - by * baseboardwidth;
     //-------------------------------------------------------------------------
     nWC.z = WC.z;
     return nWC;
   }
   //---------------------------------------------------------------------------
   // Adjust baseboard from one WorlCoordinate to another
   //---------------------------------------------------------------------------
   public WorldCoordinate 
          WCoordAdjustBaseboard(WorldCoordinate frWC, WorldCoordinate toWC)
   {
     WorldCoordinate adjWC;
     //-------------------------------------------------------------------------
     adjWC.x = toWC.x + (frWC.baseboardX - toWC.baseboardX)*baseboardwidth;
     adjWC.baseboardX = frWC.baseboardX;
     adjWC.y = toWC.y + (frWC.baseboardY - toWC.baseboardY)*baseboardwidth;
     adjWC.baseboardY = frWC.baseboardY;
     adjWC.z = toWC.z;
     //-------------------------------------------------------------------------
     return adjWC;
   }
   //---------------------------------------------------------------------------
   // Generate soup representation from Orientation
   //---------------------------------------------------------------------------
   public Soup OrientationToSoup(Orientation rot)
   {
     Soup sp = Constructors.NewSoup();
     sp.SetNamedTag("gx",rot.rx * 1800.0 / Math.PI);
     sp.SetNamedTag("gy",rot.ry * 1800.0 / Math.PI);
     sp.SetNamedTag("gz",rot.rz * 1800.0 / Math.PI);
     sp.SetNamedTag("rx",rot.rx);
     sp.SetNamedTag("ry",rot.ry);
     sp.SetNamedTag("rz",rot.rz);
     return sp;
   }
   //---------------------------------------------------------------------------
   // Generate Orientation from its soup representation
   //---------------------------------------------------------------------------
   public Orientation OrientationFromSoup(Soup sp)
   {
     Orientation OR;
     OR.rx = sp.GetNamedTagAsFloat("rx");
     OR.ry = sp.GetNamedTagAsFloat("ry");
     OR.rz = sp.GetNamedTagAsFloat("rz");
     return OR;
   }
   //---------------------------------------------------------------------------
   // Adjust location and orientation to even mm and even 1/10° inplace
   //---------------------------------------------------------------------------
   public bool AutoAdjustTransformation(WorldCoordinate W, Orientation O)
   {
     float rd;
     bool changed = false;
     if (W.x<0) rd=-0.0005; else rd=0.0005;   // rounding to full [mm]
     int wcx = (W.x+rd) * 1000.0;
     if (W.y<0) rd=-0.0005; else rd=0.0005;
     int wcy = (W.y+rd) * 1000.0;
     if (W.z<0) rd=-0.0005; else rd=0.0005;
     int wcz = (W.z+rd) * 1000.0;
     // rounding to full [1/10°] - here only z-axis, x,y-axis are set to zero
     // to make shure the CTS is horizontal plane
     if (O.rz<0) rd=-0.5; else rd=0.5;
     int orz = O.rz * 1800.0 / Math.PI + rd;
     if (W.x != wcx/1000.0) {W.x = wcx/1000.0; changed=true; }
     if (W.y != wcy/1000.0) {W.y = wcy/1000.0; changed=true; }
     if(W.z != 0.0) { W.z = 0.0; changed=true; }
     if(O.rx != 0.0) { O.rx = 0.0; changed=true; }
     if(O.ry != 0.0) { O.ry = 0.0; changed=true; }
     if(O.rz != orz * Math.PI / 1800.0) 
     { O.rz = orz * Math.PI / 1800.0; changed=true; }
     //-------------------------------------------------------------------------
     return changed;
   }
   //---------------------------------------------------------------------------
   // Auto adjust WorldCoordinate and Orientation to [mm] and [1/10°]
   // Adjusts the object and returns original and adjusted WC and OR as soup.
   //---------------------------------------------------------------------------
   public Soup AutoAdjustMapObject(MapObject M)
   {
     //-------------------------------------------------------------------------
     // M = cast<MapObject>(me);
     //-------------------------------------------------------------------------
     bool changed = false;    
     Soup sp = Constructors.NewSoup();
     WorldCoordinate W = M.GetMapObjectPosition();
     Orientation     O = M.GetMapObjectOrientation();
     sp.SetNamedSoup("OriginalLocationSoup", WCoordToSoup(W));
     sp.SetNamedSoup("OriginalOrientationSoup", OrientationToSoup(O));
     changed = AutoAdjustTransformation(W,O);
     M.SetMapObjectPosition(W);
     M.SetMapObjectOrientation(O);
     sp.SetNamedSoup("AdjustedLocationSoup", WCoordToSoup(W));
     sp.SetNamedSoup("AdjustedOrientationSoup", OrientationToSoup(O));
     sp.SetNamedTag("changed", changed);
     //-------------------------------------------------------------------------
     return sp;
   }
 };

Personal tools