Does somebody know if there exists yet a similar driver command? (in #1)
Does somebody have remarks on this idea or want to tell pros and cons? (in #1)
Considering the previous contributions and regardless of its usefulness, is there anything that makes the idea absurd or insoluble in contradiction with other assets? (in #16)
There I got no arguments against this idea. So I now start to learn more about creating commands and will post here some steps on my way.
It would be very nice, if some of you would go along with me to have a look over it, to avoid me to run into
a dead-end street, or taking words that are incorrect, or false while commenting my road steps.
You are welcome to comment my blog posts of course.
The command is designed to hold one name parameter strings (driver name or train name). If a train driver executes this command, driver or train name is set to this holded resulting string.
While editing the command it's possible to set the parameter strings (see Wiki String Table and stringtable.gs) and its fitting parameters.
If the parameter is a natural number (parameter's string holds only digits) it is considered as a number with the possibilities to be incremented/decremented every n-th call, starting at an offset to the given number.
How the menu may look like?
The menu to edit this command (here in the asset details style with the Trainz internal HTML browser) could be finally this (without any sense of the names for now):
New driver name parameter string shall be 'New %0 Parameter %1 Name %2 String %3'.
StringParam %0 is 'ParamText'.
StringParam %1: Basic value is '777' and will be incremented by '0' after every call,
starting with offset '0' and looping about '0' calls. Current value is '777'.
( reset parameter: yes )
StringParam %2: Basic value is '888' and will be incremented by '1' after every call,
starting with offset '0' and looping about '3' calls. Current value is '888'.
( reset parameter: yes )
StringParam %3: Basic value is '999' and will be decremented by '3' after every 3rd call,
starting with offset '2' and looping about '2' calls. Current value is '999'.
( reset parameter: yes )
Next call resulting driver name string is 'New ParamText Parameter 777 Name 888 String 999'
The underscored values here are links to update the values. This may look otherwise in the newer edit menus.
The 'driver' value may be driver or train. The parameterstring tells after setting it, how many params will be used. Maximum count is 10.
If the setted parameter value may be interpreted as natural number there were some more values to set. Otherwise it is handled as a normal string.
Incremented" resp. decremented by 0 handles the number as normal string, other values in this case stay unused.
The count of calls is a natural number in interval [1,10].
The offset value n indicates, that the incremention/decremention and looping will be skiped for the first n calls after reseting this parameter's values.
The looping value n indicates, that after n calls the current parameter string value will be reseted to the basic value. The looping values 0 or 1 indicate that the parameter string on no call will be changed.
First call after inserting the command uses the basic values.
Examples for the resulting strings:
Example's parameter string: 'The %0 Parameter String' Example 1:
Basic value is '777' and will be incremented by '0' after every call,
starting with offset '0' and looping about '0' calls.
Before 1. call: 'New ParamText Parameter 777 Name'
Before 2. call: 'New ParamText Parameter 777 Name'
Before 3. call: 'New ParamText Parameter 777 Name'
...
Example 2:
Basic value is '777' and will be incremented by '2' after every call,
starting with offset '0' and looping about '3' calls.
Before 1. call: 'New ParamText Parameter 777 Name'
Before 2. call: 'New ParamText Parameter 779 Name'
Before 3. call: 'New ParamText Parameter 781 Name'
Before 4. call: 'New ParamText Parameter 777 Name'
Before 5. call: 'New ParamText Parameter 779 Name'
Before 6. call: 'New ParamText Parameter 781 Name'
Before 7. call: 'New ParamText Parameter 777 Name'
...
Example 3:
Basic value is '777' and will be incremented by '3' after every call,
starting with offset '2' and looping about '3' calls.
Before 1. call: 'New ParamText Parameter 777 Name'
Before 2. call: 'New ParamText Parameter 777 Name'
Before 3. call: 'New ParamText Parameter 777 Name'
Before 4. call: 'New ParamText Parameter 780 Name'
Before 5. call: 'New ParamText Parameter 783 Name'
Before 6. call: 'New ParamText Parameter 786 Name'
Before 7. call: 'New ParamText Parameter 777 Name'
Before 8. call: 'New ParamText Parameter 780 Name'
Before 9. call: 'New ParamText Parameter 783 Name'
...
Next steps
The first version (1) uses parameters only as strings.
A second version (2) could add handling the numerical parameters to allow incrementing/decrementing every call.
A third version (3) could add a step value for incrementing/decrementing and a offset value for starting incrementing/decrementing.
But the very starting version (0) should be the basic template for further steps and a kind of dummy command that nothing does.
Version (0.1) <kuid:215489:100072>: Files we need to create a dummy command
I have used the following commands toadapt some of the code for the following script files and the config.txt, modified it more or less, or I was inspired by the existing content:
"Navigate To", "Navigate To Trackmark", "Navigate Via Trackmark", and "Drive Schedule" created by @Auran
"NavigateToTrackmarkList v2 (SP2 and later)", and "NavigateToStation v2 (SP3 and later)" created by @pguy
And of course the mostly usefull of the Wiki and the Forum were very helpful.
An asset icon 240x180 px (here renamedrivertrainasseticon.png)
Picture Thread 180317 3a
A texture icon 64x64 px (here renamedrivertraintextureicon.png)
Picture Thread 180317 3b
A texture.txt file (here renamedrivertrain.texture.txt)
This is a two line plain text file to tell the CM how to use texture file to create the texture to show the command inside the drivers command bar.
kuid <kuid:215489:100072>
kind "drivercommand"
trainz-build 4.5
script "renamedrivertraincommand"
class "RenameDriverTrainCommand"
category-class "YD"
username "Rename Driver Train"
supports-null-driver-character 1
properties-behavior "edit-after-add"
string-table
{
description "Driver command that allows to rename driver and train names for current trains and driver executing this command."
menuentryname "Click to insert and open property menus"
}
kuid-table
{
command-sounds <kuid:-3:10219>
}
thumbnails
{
0
{
image "renamedrivertrainasseticon.png"
width 240
height 180
}
}
A asset class script file (here renamedrivertraincommand.gs)
Code:
//=============================================================================
// File: RenameDriverTrainCommand.gs
// Desc: The methods of this classes create the (empty) driver command and
// insert it to the drivers command bar and command list.
//=============================================================================
include "DriverCommand.gs"
include "World.gs"
include "KUID.gs"
//=============================================================================
// Class: DummyCommand
// Desc: Minimum definition of a driver command class. This command does
// nothing and is only a helper object providing failing the command.
//=============================================================================
class DummyCommand isclass CustomCommand // see schedule.gs
{
public bool Execute(Train train, int px, int py, int pz) { return true; }
};
//=============================================================================
// Class: RenameDriverTrainCommand
// Desc: Generating the 'Rename Driver Train' driver command and inserting it
// to the command list of the driver.
//=============================================================================
class RenameDriverTrainCommand isclass DriverCommand
{
//=============================================================================
// Name: Init (Called by native code)
// Desc: Adds in this commands case a message handler to get knowledge about
// selecting this command and inserting at to the drivers command bar.
//=============================================================================
public void Init(Asset asset)
{
inherited(asset);
// ("MenuItemSelected", "", paramSoup) is a system message that
// delivers an objects id and name within the message's paramSoup.
// (see AddCommandMenuItem method here and menu.gs)
AddHandler(me, "MenuItemSelected", null, "OnMenuItemSelected");
}
//=============================================================================
// Name: AddCommandMenuItem (Called by native code)
// Desc: Builds a menu item (including submenus) for this driver command to be
// added into the command list.
//=============================================================================
public void AddCommandMenuItem(DriverCharacter driver, Menu menu)
{
// Should be proofed!
// May be we need no menu because we edit the command direct after inserting.
// May be that we need a empty submenu entry to be click to get the message
// to be informed that the command was inserted and than open the properties
// menu.
/* --------------------------------------------------------------------------
StringTable strTable = GetAsset().GetStringTable();
menu.AddSubmenu(strTable.GetString("menuentryname"), emptyMenu);
// ----------------------------------------------------------------------- */
}
//=============================================================================
// Name: PlayConfirmation
// Desc: Plays the sound for confirming acoustically the menu input click
// using a script from a script library. Used in message handler
// 'OnMenuItemSelected'.
//=============================================================================
void PlayConfirmation(void)
{
KUID kuid = GetAsset().LookupKUIDTable("command-sounds");
Library libCommandSounds = World.GetLibrary(kuid);
if (libCommandSounds)
{
libCommandSounds.LibraryCall("PlayConfirmation", null, null);
}
}
//=============================================================================
// Name: CreateScheduleCommand
// Desc: Creates the driver command instance to be inserted into the driver
// command schedule list.
//=============================================================================
DriverScheduleCommand CreateScheduleCommand(DriverCharacter driver, Soup soup)
{
// ----------------------------------------------------------------------- */
// We here use only a dummy (empty) schedule command
DriverScheduleCommand cmd = new DriverScheduleCommand();
return cmd;
}
//=============================================================================
// Name: OnMenuItemSelected
// Desc: Message handler to react on menu entry selection.
//=============================================================================
void OnMenuItemSelected(Message msg)
{
// GetDriverCommands is a helper method (see: drivercommand.gs)
DriverCommands commandList = GetDriverCommands(msg);
DriverCharacter driver = cast<DriverCharacter>(msg.src);
// We don't need any response from the menu but only have to append
// our DummyCommand.
Soup commandSoup = Constructors.NewSoup();
DriverScheduleCommand cmd = CreateScheduleCommand(driver, commandSoup);
commandList.AddDriverScheduleCommand(cmd);
// Play a sound tell us the click acousticaly
if (driver) PlayConfirmation();
}
};
//=============================================================================
// Class: RenameDriverTrainScheduleCommand
// Desc:
//=============================================================================
class RenameDriverTrainScheduleCommand isclass DriverScheduleCommand
{
RenameDriverTrainCommand m_parent;
public bool BeginExecute(DriverCharacter driver)
{
// Processing our (empty) schedule using a dummy empty command.
driver.DriverCustomCommand(new DummyCommand());
driver.DriverIssueSchedule();
return true;
}
};
What does this version 0.1 do?
No more no less than adding a entry into the list of commands.
Next bricks to the knowledge about driver commands
Now that I got the base run, I want to understand how the things work together.
For now I understand that there are three "main" classes, a CustomCommand class, a DriverCommand class, and a DriverScheduleCommand class. But what they are for and what they are not for isn't very clear to me for now and the Trainz Wiki doesn't give deeper insights to me, or didn't I search pointedly enough?
The only one information is the config tags site for KIND DriverCommand.
So this leads me to look into the comments of the Trainz scripts:
schedule.gs for CustomCommand class
drivercommand.gs for DriverCommand class
driverschedulecommand.gs for DriverScheduleCommand class
And I found one more:
drivercommands.gs for DriverCommands class
Therein were the classes Command, Schedule, CustomCommand, DriverCommand, DriverCommands and DriverScheduleCommand.
Some detailed look into the script-code and the comments there brought some more light into the things.
We need a CustomCommand that does what it shall do
A Schedule-class instance-object holds an array of Command-class instance-objects.This list builds the task (schedule or array of commands) a train shall do. As we may see in the "Train::RunSchedule( ... )" method, such schedules are attached to trains, one schedule for one train at once.
This (final-)class holds a couple of methods to append built-in Command-class instance-objects to its commands-array as well as some methods to organise this commands- list (array).
These available command types can be viewed as command-atoms, from which the schedule-molecules can be composed. At the same time, however, this class also limits the number of available command-atoms that can be used. But this isn't a highly valued limit.
To be able to issue more user-built (driver-)commands, there is a "plug-in" method too, called "custom", to be able to append these user-build Command-class instance-objects.
So therefore we create a CustomCommand derived class to be able to create our CustomCommand. In our case its "class RDT_CustomCommand isclass CustomCommand { ... }". The CustomCommand class has two member methods to be overridden with what our driver command shall do on the run.
The first method "execute( ... )" will be called by a train and contains what the custom command should do while the schedule runs. The standard behavior does nothing else than returning false. The second method "ShouldStopTrainOnCompletion()" tells the caller what the name says. The standard behavior returns true.
(???) For now I don't know if this is related to CustomCommand-execute-method success or the schedule's success. Think it's the first one. Or is it only used if this custom command is the last in the schedule's list?
Parameters, the command shall care about
A Command-class instance-object holds a data structure that will be used and delivered by the Schedule-class methods to append commands to the Schedule-object. Not all such methods use all the parameters. The CustomCommand's execute method's signature is "public bool execute(Train, int, int, int)".
To distinguish the parameters of the execute method from other parameters that we will use in our driver command, I call them the execute-parameters.
(???) For now I'm not able to see what purpose the three int execute-parameters have and from where they will get values.
For our driver command the three int execute-parameters wont be used.
The RenameDriverTrain-Parameters
The editable positions of the RenameDriverTrain driver command instance (our RenameDriverTrain-Parameters) we load and store by a Soup object using the SetProperties resp. GetProperties methods.
This is the "external" representation of these parameters. To represent them as script-intern we create two data-structure classes, one for the "outer" soup and one for the parameter sub-soups.
Code:
//=============================================================================
// Class: RDT_ParameterData
// Desc: Data structure of a parameters of the command's parameter string
//=============================================================================
class RDT_ParameterData
{
public string BasicString = "ParameterText";
public string CurrentString = "ParmaterText";
public string StepDirection = "unused";
public int StepValue = 0;
public int CallStep = 0;
public int CallOffset = 0;
public int LoopCalls = 0;
};
Code:
//=============================================================================
// Class: RDT_CustomCommandData
// Desc: Data structure of the command's parameter string
//=============================================================================
class RDT_CustomCommandData
{
public string ParameterString = "ParameterString";
public int ParameterCount = 0;
public RDT_ParameterData[] StringParameters = new RDT_ParameterData()[0];
};
So until now we have discussed the internal and external parameter representations, created two data classes and the custom command class to execute our driver command as a dummy command.
But now I've lost the thread, possibly due to too much input in too short a time and/or I've forgotten things that I already knew, and/or ... So I take the time to study some more driver command scripts and/or Trainz scripts.
Version (1.0) <kuid2:215489:100072:1>: Files we need to create a dummy command
For now I added mandatory methods.
The texture.txt file I removed for this version
It looks like the texture.txt version and the thumbnails container entry at once are not allowed.
And for both variants I didn't found a solution to show this command bar icon.
The config.txt file
Code:
kuid <kuid2:215489:100072:1>
kind "drivercommand"
trainz-build 4.5
script "renamedrivertraincommand"
class "RDT_Command"
category-class "YD"
username "Rename Driver Train"
supports-null-driver-character 1
properties-behavior "edit-after-add"
string-table
{
description "Driver command that allows to rename driver and train names for current trains and driver executing this command."
menuentryname "Click to insert and open property menus"
}
thumbnails
{
0
{
image "renamedrivertraintextureicon.png"
width 64
height 64
}
1
{
image "renamedrivertrainasseticon.png"
width 240
height 180
}
}
kuid-table
{
command-sounds <kuid:-3:10219>
}
A asset class script file (here renamedrivertraincommand.gs) part 1
Code:
//=============================================================================
// File: RenameDriverTrainCommand.gs
// Desc: The methods of this classes create the (empty) driver command and
// insert it to the drivers command bar and command list.
// For shorter names "RenameDriverTrain" will be used as "RDT_".
//=============================================================================
Code:
include "DriverCommand.gs"
include "World.gs"
include "KUID.gs"
Code:
//=============================================================================
// Class: RDT_ParameterData
// Desc: Data structure of a parameters of the command's parameter string
//=============================================================================
Code:
class RDT_ParamData
{
public string BasicString = "ParameterText";
public string CurrentString = "ParmaterText";
public string StepDirection = "unused";
public int StepValue = 0;
public int CallStep = 0;
public int CallOffset = 0;
public int LoopCalls = 0;
};
Code:
//=============================================================================
// Class: RDT_CustomCommandData
// Desc: Data structure of the command's parameter string
//=============================================================================
Code:
class RDT_CommandData
{
public string ParameterString = "ParameterString";
public string SetNameOf = "driver";
public int ParameterCount = 0;
public RDT_ParameterData[] StringParameters = new RDT_ParameterData[0];
};
Code:
//=============================================================================
// Class: RDT_CustomCommand
// Desc: Minimum definition of a driver command class. This command does
// nothing and is only a helper object providing failing the schedule.
//=============================================================================
Code:
class RDT_CustomCommand isclass CustomCommand // CustomCommand see schedule.gs
{
// We do nothing but telling that all went well.
public bool Execute(Train train, int px, int py, int pz)
{
return true;
}
};
The asset class script file (here renamedrivertraincommand.gs) part 2
Code:
//=============================================================================
// Class: RDT_ScheduleCommand
// Desc:
//=============================================================================
class RDT_ScheduleCommand isclass DriverScheduleCommand
{
public RDT_CommandData RDT_data;
//=============================================================================
public bool BeginExecute(DriverCharacter driver)
{
// Issue a RDT_command to do the things it shell do.
driver.DriverCustomCommand(new RDT_CustomCommand());
driver.DriverIssueSchedule();
return true;
}
//=============================================================================
Soup GetUserInterfaceProperties()
{
Soup UIP_Soup = inherited();
Soup RDT_UIP_Soup = Constructors.NewSoup();
{ // Build the RDT uip-soup from RDT_CommandData and return it.
// In this version we don't care about parameters and us the
// ParameterString string as is.
Soup propID = Constructors.NewSoup();
{
// ParameterString as uip type "string"
propID.SetNamedTag("name","Set the parameter string: ");
propID.SetNamedTag("type","string");
propID.SetNamedTag("value",RDT_data.ParameterString);
}
RDT_UIP_Soup.SetNamedSoup("ParameterString",propID);
{
// SetNameOf as uip type "list" with two values "driver", "train"
propID.SetNamedTag("name","Rename the name of: ");
propID.SetNamedTag("type","list");
propID.SetNamedTag("value",RDT_data.SetNameOf);
Soup listOptions = Constructors.NewSoup();
{
// Set the propID's list options
listOptions.SetNamedTag(0,"driver");
listOptions.SetNamedTag(1,"train");
}
propID.SetNamedSoup("list-options",listOptions);
}
RDT_UIP_Soup.SetNamedSoup("SetNameOf",propID);
}
UIP_Soup.SetNamedSoup("uip_soup", RDT_UIP_Soup);
return UIP_Soup;
}
//=============================================================================
bool SetUserInterfaceProperty(string propID, Soup propData)
{
// Store edited value from the propData to the RDT_CommandData member
// variable fitting to the delivered propID and return true if success.
if (propID == "ParameterString")
{
RDT_data.ParameterString = propData.GetNamedTag("value");
return true;
}
if (propID == "SetNameOf")
{
RDT_data.ParameterString = propData.GetNamedTag("value");
return true;
}
return inherited(propID, propData);
}
//=============================================================================
public object GetIcon(void)
{
return cast<object>(m_driverCommand);
}
//=============================================================================
public string GetTooltip(void)
{
//
// TBD: Build the tooltip string from RDT_CommandData and return it
//
string tt = "RDT Tooltip String Text";
return tt;
}
//=============================================================================
public Soup GetProperties(void)
{
Soup paramsSoup = Constructors.NewSoup();
int i;
for(i = 0; i < RDT_data.StringParameters.size(); i++)
{
Soup paramSoup = Constructors.NewSoup();
{
paramSoup.SetNamedTag("BasicString", RDT_data.StringParameters[i].BasicString);
paramSoup.SetNamedTag("CurrentString", RDT_data.StringParameters[i].CurrentString);
paramSoup.SetNamedTag("StepDirection", RDT_data.StringParameters[i].StepDirection);
paramSoup.SetNamedTag("StepValue", RDT_data.StringParameters[i].StepValue);
paramSoup.SetNamedTag("CallStep", RDT_data.StringParameters[i].CallStep);
paramSoup.SetNamedTag("CallOffset", RDT_data.StringParameters[i].CallOffset);
paramSoup.SetNamedTag("LoopCalls", RDT_data.StringParameters[i].LoopCalls);
}
paramsSoup.SetNamedSoup(i, paramSoup);
}
Soup RDT_data_soup = Constructors.NewSoup();
{
RDT_data_soup.SetNamedTag("ParameterString", RDT_data.ParameterString);
RDT_data_soup.SetNamedTag("SetNameOf", RDT_data.SetNameOf);
RDT_data_soup.SetNamedTag("ParameterCount", RDT_data.ParameterCount);
RDT_data_soup.SetNamedSoup("StringParameters", paramsSoup);
}
return RDT_data_soup;
}
//=============================================================================
public void SetProperties(Soup soup)
{
if (soup.GetNamedTag("ParameterString"))
RDT_data.ParameterString = soup.GetNamedTag("ParameterString");
if (soup.GetNamedTag("SetNameOf"))
RDT_data.SetNameOf = soup.GetNamedTag("SetNameOf");
RDT_data.ParameterCount = soup.GetNamedTagAsInt("ParameterCount");
Soup paramsSoup = soup.GetNamedSoup("StringParameters");
int i;
for (i = 0; i < RDT_data.ParameterCount; i++)
{
Soup paramSoup = paramsSoup.GetNamedSoup(i);
RDT_data.StringParameters[i] = new RDT_ParameterData();
{
RDT_data.StringParameters[i].BasicString = paramSoup.GetNamedTag("BasicString");
RDT_data.StringParameters[i].CurrentString = paramSoup.GetNamedTag("CurrentString");
RDT_data.StringParameters[i].StepDirection = paramSoup.GetNamedTag("StepDirection");
RDT_data.StringParameters[i].StepValue = paramSoup.GetNamedTagAsInt("StepValue");
RDT_data.StringParameters[i].CallStep = paramSoup.GetNamedTagAsInt("CallStep");
RDT_data.StringParameters[i].CallOffset = paramSoup.GetNamedTagAsInt("CallOffset");
RDT_data.StringParameters[i].LoopCalls = paramSoup.GetNamedTagAsInt("LoopCalls");
}
}
}
};
The asset class script file (here renamedrivertraincommand.gs) part 3
Code:
//=============================================================================
// Class: RDT_Command
// Desc: Generating the 'Rename Driver Train' driver command and inserting it
// to the command list used for inserting it by the drivers schedule bar.
//=============================================================================
[SPOILER=class RDT_Command ]
class RDT_Command isclass DriverCommand
{
//=============================================================================
// Name: Init (Called by native code)
// Desc: Nothing to here.
//=============================================================================
public void Init(Asset asset)
{
inherited(asset);
}
//=============================================================================
// Name: CreateScheduleCommand
// Desc: Creates the driver command instance to be inserted into the driver
// command schedule list.
//=============================================================================
DriverScheduleCommand CreateScheduleCommand(DriverCharacter driver, Soup soup)
{
// ----------------------------------------------------------------------- */
// We here use only a dummy (empty) schedule command
RDT_ScheduleCommand cmd = new RDT_ScheduleCommand();
cmd.Init(driver, me);
cmd.SetProperties(soup);
return cast<DriverScheduleCommand>cmd;
}
//=============================================================================
// Name: PlayConfirmation
// Desc: Plays the sound for confirming acoustically the menu input click
// using a script from a script library. Used in message handler
// 'OnMenuItemSelected'.
//=============================================================================
void PlayConfirmation(void)
{
KUID kuid = GetAsset().LookupKUIDTable("command-sounds");
Library libCommandSounds = World.GetLibrary(kuid);
if (libCommandSounds)
{
libCommandSounds.LibraryCall("PlayConfirmation", null, null);
}
}
};
The things look like version 0.1. No icon in the command bar. And the comands parameter UI isn't visible too.
So the search and code reading stays on.
Next steps to walk on - What is the behavior of the code and how it will be triggered
My father told me multiple times to avoid me giving up:
"If you don't get adequate answers, it's up to you to untangle the ball of wool. Find the beginning of the thread and then work your way forward step by step until the ball of wool is unraveled."
This is a task of the Driver Command Rule. If we set our command allowed, we may consider it as activating it, the Init method of the DriverCommand class instance is called.
In my mind there is no usecase here to do something more as the minimum requiered. We have to use the inherited(...) method an will set the m_assset instance variable.
Question: How will the mouse click event be handled?
If we choose our command by mouse click the native code calls the AddCommandMenuItem(...) method of the DriverCommand class instance. It's code may append additionally submenus to choose some parameters of the DriverCommand. We want edit the parameters yust after inserting it (see config.txt) to the command bar and later by click on the icon there.
Furthermore the CreateScheduleCommand(...) method will be called to create the DriverScheduleCommand class instance to handle the driver commands task by the "atomic" CustomCommand's.
This creating runs the Init(...) method of this DriverScheduleCommand instance to initiate the parameters and setting the m_driverCharacter variable with the driver object and the m_driverCommand variable with the parent object. Since in our case all parameters were initiated with standard values, we only initiate the two instance variables.
Sub-Question: What usecases has the parent object information?
Background note: If a driver command will be inserted to a command bar, there will be created an instance of this DriverCommand. From this instance will be created a DriverScheduleCommand class instance.
Since the command could be used multiple times it is useful to know which instances belong together. From the sight of the DriverScheduleCommand class instance its caller is the parent.
Question: How does the command bar icon come into the command bar?
Therefore the DriverScheduleCommand class holds the GetIcon() method to deliver information about the icon-texture to use. This method will called by the native Trainz code.
Question: How does the command bar icon have to be provided to it?
The method GetUserInterfaceProperties declares a edit menu structure for the command properties to be used. After editing a property the Trainz native code calls the SetUserInterfaceProperty[/] method to handle the the edited property.
Question: How will the command's properties user interface be shown to edit properties?
The native Trainz code call the methods GetProperties and SetProperties to save and load a property soup containing the Prperty values. It is up to the code creators to provide this methods.
Question: How the edited properties will be handled and delivered to the code?
If one edit a property, one has to quit the changes. This will call the SetUserInterfaceProperty method of the DriverScheduleCommand class to handle the changed property.
Question: How will the command's properties user interface be closed after editing properties?
There for we create one or more "atomic" CustomCommand class and there in the Execute(...) method. The content of this method is what this "atomic" CustomCommand does.
The inserting of the one or more "atomic" CustomCommands and prebuilded Comands into a trains schedule (the task molekule) will reflect the task of our Driver Command.
Hint: These "atomic" commands and its (train-)schedule "molecule" code, class resp. member variables and methods, one may find in schedule.gs.
The next two posts contain the current code (version 2.0 <KUID2:215489:100072:2>). I have splitted the code into two files. The findings from the questions above have been incorporated. But unfortunately the inserted command is still not displayed .
kuid <kuid2:215489:100072:3>
kind "drivercommand"
trainz-build 4.5
script "rdt_command"
class "RDT_Command"
category-class "YD"
username "Rename Driver||Train"
supports-null-driver-character 1
properties-behavior "edit-after-add"
description "Driver command that allows to rename driver or train name
for the current driver/train executing this command.
RDT means Rename Driver or Train."
thumbnails
{
0
{
image "renamedrivertraintextureicon.png"
width 64
height 64
}
1
{
image "renamedrivertrainasseticon.png"
width 240
height 180
}
}
kuid-table
{
command-sounds <kuid:-3:10219>
}
Code:
//=============================================================================
// File: RDT_Command.gs
// Desc: The methods of this classes create the (empty) driver command and
// insert it to the drivers command bar and command list.
// For shorter names "RenameDriverTrain" will be used as "RDT_".
// Needs including RDT_ScheduleCommand.gs.
//=============================================================================
include "DriverCommand.gs"
include "RDT_ScheduleCommand.gs"
//=============================================================================
// Class: RDT_ParameterData
// Desc: Data structure for thre parameters of the command's parameter string
//-----------------------------------------------------------------------------
class RDT_ParameterData
{
public string BasicString = "ParameterText";
public string CurrentString = "ParmaterText";
public string StepDirection = "unused";
public int StepValue = 0;
public int CallStep = 0;
public int CallOffset = 0;
public int LoopCalls = 0;
};
//=============================================================================
//=============================================================================
// Class: RDT_CustomCommandData
// Desc: Data structure of the command's parameter string
//-----------------------------------------------------------------------------
class RDT_CommandData
{
public string ParameterString = "ParameterString";
public string SetNameOf = "driver";
public int ParameterCount = 0;
public RDT_ParameterData[] StringParameters = new RDT_ParameterData[0];
};
//=============================================================================
//=============================================================================
// Class: RDT_CustomCommand
// Desc: A CustomCommand is a "atomic" command that may considered as a "brick"
// or step to fulfill the task of the driver command. There may be more
// CustomCommands. There are some more predefined "atomic" commands
// (see schedule.gs).
// These "atimic" commands were scheduled by the DriverScheduleCommand
// instance to the "inner" schedule, a "atimic" command list. This
// "inner" schedule is attached to the train the driver resides on.
// For CustomCommand class see schedule.gs.
//-----------------------------------------------------------------------------
class RDT_CustomCommand isclass CustomCommand
{
//===========================================================================
// Name: Execute (Called by native Trainz code)
// Desc: Here we do what our "atomic" command shall do.
// The three int parameters of the Execute method we may set while
// calling the Custom method to insert it in the trains schedule list.
//---------------------------------------------------------------------------
public bool Execute(Train train, int px, int py, int pz)
{
Interface.Log("_RDT_CustomCommand - Execute method");
// We want to use the DriverScheduleCommand's property soup to use the
// parameters to create the current name, assigne it and handle the
// incrementing/decrementing string parameters as well as save its new
// current values.
// We do for now nothing but telling that all went well.
return true;
} /* End of Execute */
//===========================================================================
// Name: ShouldStopTrainOnCompletion
// ( ??? will be called by native Trainz code ??? )
// Desc: ??? more than the method's name tells ???
//---------------------------------------------------------------------------
public bool ShouldStopTrainOnCompletion()
{
return true;
} /* End of ShouldStopTrainOnCompletion */
};
//=============================================================================
//=============================================================================
// Class: RDT_Command
// Desc: Generating the 'Rename Driver Train' driver command and inserting it
// to the driver commands list used for inserting it by the drivers
// schedule bar.
// For DriverCommand class see drivercommand.gs.
//-----------------------------------------------------------------------------
class RDT_Command isclass DriverCommand
{
//===========================================================================
// Name: Init (Called by native Trainz code)
// Desc: Initiate the instance object and set member variable m_asset.
//---------------------------------------------------------------------------
public void Init(Asset asset)
{
inherited(asset);
m_asset = asset;
AddHandler(me, "RDT_Cmd_Item", null, "RDT_CommandHandler");
Interface.Log("_RDT_Command - Init method");
} /* End of Init */
//===========================================================================
// Name: AddCommandMenuItem (Called by native Trainz code)
// Desc: Adds this driver command as a menu item for a driver character's
// selective driver command list inside the command bar.
//---------------------------------------------------------------------------
public void AddCommandMenuItem(DriverCharacter driver, Menu menu)
{
Interface.Log("_RDT_Command - AddCommandMenuItem method");
menu.AddItem("RDT Command", me, "RDT_Cmd_Item", "RDT_Cmd");
} /* End of AddCommandMenuItem */
//===========================================================================
// Name: CreateScheduleCommand (Called by message handler)
// Desc: Creates the driver command instance to be inserted into the driver
// command schedule list.
// The comment in drivercommand.gs tells "This method is called by
// Trainz to create a DriverScheduleCommand object for this command."
// Note: But it seems that this comment fails (???) since it not writes
// about "native Trainz code".
//---------------------------------------------------------------------------
DriverScheduleCommand CreateScheduleCommand(DriverCharacter driver, Soup soup)
{
Interface.Log("_RDT_Command - CreateScheduleCommand method");
// The RDT_ScheduleCommand combines needed "atomic" commands
// and our custom commands to fulfill the task of our driver command.
// Here we initalise our ScheduleComand instance.
RDT_ScheduleCommand cmd = new RDT_ScheduleCommand();
cmd.Init(driver, me);
cmd.SetProperties(soup);
return cast<DriverScheduleCommand>cmd;
} /* End of CreateScheduleCommand */
//===========================================================================
// Name: PlayConfirmation (Called by native Trainz code)
// Desc: Play a sound to confirm that a menu item was selected.
//---------------------------------------------------------------------------
void PlayConfirmation(void)
{
Interface.Log("_RDT_Command - PlayConfirmation method");
KUID kuid = GetAsset().LookupKUIDTable("command-sounds");
Library libCommandSounds = World.GetLibrary(kuid);
if (libCommandSounds) {
libCommandSounds.LibraryCall("PlayConfirmation", null, null);
}
} /* End of PlayConfirmation */
//===========================================================================
// Name: RDT_CommandHandler Mesage handler for "RDT_Cmd_Item", * messages
// Desc: Mesage handler for the only "RDT_Cmd_Item", "RDT_Cmd" message.
//---------------------------------------------------------------------------
void RDT_CommandHandler(Message msg)
{
Interface.Log("_RDT_Command - RDT_CommandHandler method");
// Since we deal with only one message there is no need to handle multiple
// major, minor pairs.
DriverCharacter driver = cast<DriverCharacter>(msg.src);
DriverCommands commands = GetDriverCommands(msg);
Soup soup = Constructors.NewSoup();
// Create the DriverScheduleCommand class instance for this RDT_Command
RDT_ScheduleCommand cmd = cast<RDT_ScheduleCommand>CreateScheduleCommand(driver, soup);
// Add the new created DriverScheduleCommand class instance to the
// drivers's driver commands list (driver's schedule).
// (???) This let our command appear in the command bar(???)
commands.AddDriverScheduleCommand(cmd);
// Play the confirmation sound
if(driver) PlayConfirmation();
} /* End of RDT_CommandHandler */
};
//=============================================================================
//=============================================================================
// File: RDT_ScheduleCommand.gs
// Desc: This script file will be included into RDT_ScheduleCommand.gs script.
// For shorter names "RenameDriverTrain" will be used as "RDT_".
// Needs including RDT_Command.gs to compile successful.
//=============================================================================
include "DriverScheduleCommand.gs"
include "RDT_Command.gs"
//=============================================================================
// Class: RDT_ScheduleCommand
// Desc: Schedule (dispatch) custom and/or other "atomic" commands into the
// trains schedule list.
// For DriverScheduleCommand class see driverschedulecommand.gs.
//-----------------------------------------------------------------------------
class RDT_ScheduleCommand isclass DriverScheduleCommand
{
public RDT_CommandData RDT_data;
//===========================================================================
// Name: Init (called by the DriverCommand class instance)
// Desc: Initialise the DriverScheduleCommand class instance and
// set the instance variables m_driverCharacter and m_driverCommand
// Because there are multiple DriverCommand class instances in case the
// driver command is used multiple times in the same or other command
// bars, it is a good idea to let this DriverScheduleCommand class
// instance know its parent DriverCommand class instance.
//---------------------------------------------------------------------------
public void Init(DriverCharacter driver, DriverCommand parent)
{
Interface.Log("_RDT_ScheduleCommand - Init method");
inherited(driver, parent);
m_driverCharacter = driver;
m_driverCommand = parent;
} /* End of Init */
//===========================================================================
// Name: BeginExecute
// Desc:
//---------------------------------------------------------------------------
public bool BeginExecute(DriverCharacter driver)
{
Interface.Log("_RDT_ScheduleCommand - BeginExecute method");
// Issue a RDT_command to do the things it shell do.
driver.DriverCustomCommand(new RDT_CustomCommand());
driver.DriverIssueSchedule();
return true;
} /* End of BeginExecute */
//===========================================================================
// Name: GetUserInterfaceProperties ( ??? Who calls it and whenn ??? )
// Desc: Creates a user interface properties soup, defining property values
// to be used in an user interface proprty edit menu.
//---------------------------------------------------------------------------
public Soup GetUserInterfaceProperties()
{
Interface.Log("_RDT_ScheduleCommand - GetUserInterfaceProperties method");
Soup RDT_UIP_Soup = inherited();
// Build the RDT uip-soup from RDT_CommandData and return it.
// In this version we don't care about parameters and us the
// ParameterString string as is.
Soup propID = Constructors.NewSoup();
{
// ParameterString as uip type "string"
propID.SetNamedTag("name","Set the parameter string: ");
propID.SetNamedTag("type","string");
propID.SetNamedTag("value",RDT_data.ParameterString);
}
RDT_UIP_Soup.SetNamedSoup("ParameterString",propID);
{
// SetNameOf as uip type "list" with two values "driver", "train"
propID.SetNamedTag("name","Rename the name of: ");
propID.SetNamedTag("type","list");
propID.SetNamedTag("value",RDT_data.SetNameOf);
Soup listOptions = Constructors.NewSoup();
{
// Set the propID's list options
listOptions.SetNamedTag(0,"driver");
listOptions.SetNamedTag(1,"train");
}
propID.SetNamedSoup("list-options",listOptions);
}
RDT_UIP_Soup.SetNamedSoup("SetNameOf",propID);
return RDT_UIP_Soup;
} /* End of GetUserInterfaceProperties */
//=============================================================================
// Name: SetUserInterfaceProperty (Called by native Trainz code)
// Desc: After quitting a input of a property vale this method is called to
// handle the inputed values.
//---------------------------------------------------------------------------
public bool SetUserInterfaceProperty(string propID, Soup propData)
{
Interface.Log("_RDT_ScheduleCommand - SetUserInterfaceProperties method");
// Store edited value from the propData to the RDT_CommandData member
// variable fitting to the delivered propID and return true if success.
if (propID == "ParameterString")
{
RDT_data.ParameterString = propData.GetNamedTag("value");
return true;
}
if (propID == "SetNameOf")
{
RDT_data.ParameterString = propData.GetNamedTag("value");
return true;
}
return inherited(propID, propData);
} /* End of SetUserInterfaceProperty */
//=============================================================================
// Name: GetIcon (Called by native Trainz code)
// Desc: Returns the object that hold the icon to be used as texture.
//---------------------------------------------------------------------------
public object GetIcon(void)
{
Interface.Log("_RDT_ScheduleCommand - GetIcon method");
// Looks like we tell Trainz to use an icon that is foundable with the help
// of our DriverCommand class instance (the so called parent of our
// DriverScheduleCommand class instance).
return cast<object>(m_driverCommand);
} /* End of GetIcon */
//=============================================================================
// Name: GetTooltip (Called by native Trainz code)
// Desc: Shows a tooltip text while mouse over the driver commands icon inside
// the driver schedule command bar.
//---------------------------------------------------------------------------
public string GetTooltip(void)
{
Interface.Log("_RDT_ScheduleCommand - GetTooltip method");
//
// TBD: Build the tooltip string from RDT_CommandData and return it
//
string tt = "RDT Tooltip String Text";
return tt;
} /* End of GetTooltip */
//=============================================================================
// Name: GetProperties
// Desc: Create the properties soup to be saved with the DriverCommand class
// instance in the route/session.
//---------------------------------------------------------------------------
public Soup GetProperties(void)
{
Interface.Log("_RDT_ScheduleCommand - GetProperties method");
Soup paramsSoup = Constructors.NewSoup();
int i;
for(i = 0; i < RDT_data.StringParameters.size(); i++)
{
Soup paramSoup = Constructors.NewSoup();
{
paramSoup.SetNamedTag("BasicString", RDT_data.StringParameters[i].BasicString);
paramSoup.SetNamedTag("CurrentString", RDT_data.StringParameters[i].CurrentString);
paramSoup.SetNamedTag("StepDirection", RDT_data.StringParameters[i].StepDirection);
paramSoup.SetNamedTag("StepValue", RDT_data.StringParameters[i].StepValue);
paramSoup.SetNamedTag("CallStep", RDT_data.StringParameters[i].CallStep);
paramSoup.SetNamedTag("CallOffset", RDT_data.StringParameters[i].CallOffset);
paramSoup.SetNamedTag("LoopCalls", RDT_data.StringParameters[i].LoopCalls);
}
paramsSoup.SetNamedSoup(i, paramSoup);
}
Soup RDT_data_soup = Constructors.NewSoup();
{
RDT_data_soup.SetNamedTag("ParameterString", RDT_data.ParameterString);
RDT_data_soup.SetNamedTag("SetNameOf", RDT_data.SetNameOf);
RDT_data_soup.SetNamedTag("ParameterCount", RDT_data.ParameterCount);
RDT_data_soup.SetNamedSoup("StringParameters", paramsSoup);
}
return RDT_data_soup;
} /* End of GetProperties */
//===========================================================================
// Name: SetProperties
// Desc: Load values of tags of the properties soup of the DriverCommand
// class instance object to instance variables to be used in this
// command's code.
//---------------------------------------------------------------------------
public void SetProperties(Soup soup)
{
Interface.Log("_RDT_ScheduleCommand - SetProperties method");
if (soup.GetNamedTag("ParameterString"))
RDT_data.ParameterString = soup.GetNamedTag("ParameterString");
if (soup.GetNamedTag("SetNameOf"))
RDT_data.SetNameOf = soup.GetNamedTag("SetNameOf");
RDT_data.ParameterCount = soup.GetNamedTagAsInt("ParameterCount");
Soup paramsSoup = soup.GetNamedSoup("StringParameters");
int i;
for (i = 0; i < RDT_data.ParameterCount; i++)
{
Soup paramSoup = paramsSoup.GetNamedSoup(i);
RDT_data.StringParameters[i] = new RDT_ParameterData();
{
RDT_data.StringParameters[i].BasicString = paramSoup.GetNamedTag("BasicString");
RDT_data.StringParameters[i].CurrentString = paramSoup.GetNamedTag("CurrentString");
RDT_data.StringParameters[i].StepDirection = paramSoup.GetNamedTag("StepDirection");
RDT_data.StringParameters[i].StepValue = paramSoup.GetNamedTagAsInt("StepValue");
RDT_data.StringParameters[i].CallStep = paramSoup.GetNamedTagAsInt("CallStep");
RDT_data.StringParameters[i].CallOffset = paramSoup.GetNamedTagAsInt("CallOffset");
RDT_data.StringParameters[i].LoopCalls = paramSoup.GetNamedTagAsInt("LoopCalls");
}
}
} /* End of SetProperties */
};
//=============================================================================
My partner lets me tell you that she is very proud of you for successfully letting me get through things on my own. She had a wonderful time with her sisters.
That sounds a little bit sarcastic to me.
But OK, it is what it is. Back to the topic. Back to the milestone.
I found a first solution handling the editing of driver commands old style and new style. I created three dummy driver command versions. They all have only the task to log the content of the command's parameter, given from the editing menu.
The old style menu (before inserting the command to the command bar) deals only with the option selecting. The new style menu deals with all parameters.
First version <kuid2:215489:9001:1> holds only a simple text string.
Second version <kuid2:215489:9002:1> holds additionally an option string with selecting the value by an inbuilt ("fixed") option list.
Third version <kuid2:215489:9003:1> deals additionally with a dynamic count (from 0 to 10) of parameter text strings.
Additionally there is a small test route <kuid:215489:10000> and session <kuid:215489:10001> pair to demonstrate the functionality of the third command.
The source code is well commented not only for the functionality but for some technical background too. Hope the comments are pointedly enough.
Two files may be downloaded.