[MD] Some tricks and ideas

The place to discuss scripting and game modifications for X³: Terran Conflict and X³: Albion Prelude.

Moderators: Moderators for English X Forum, Scripting / Modding Moderators

Post Reply
User avatar
Posts: 6174
Joined: Fri, 8. Apr 05, 19:09

[MD] Some tricks and ideas

Post by enenra » Mon, 1. Feb 16, 00:45

Heya everyone.

I've spent a lot of time fiddling with various parts of the game. Trying to find ways around limitations, misappropriating systems, stuff like that! :D

With the MD specifically I've tested and tried quite a lot of different things. In this post I want to detail some of those tricks and ideas that may or may not be useful to other modders, or at least interesting. Here we go, in no particular order.


Typecode Traversing

The MD has no good way to actually get the typename of a ship or other object without knowing that very typename, thus hardcoding it. That is the reason the SRST library (library to tell the MD which ships to use) exists and is so incredibly unwieldy and terrible for compatability (since every mod needs to add its ships to it manually, making combining mods a pain). I wanted to come up with a way to get rid of the need for a huge file like SRST, so I had to come up with a way to go through the TShips within MD and catalogue what was there. Except the MD doesn't really have that ability at all. At least it's not supposed to. There is, for example, no way to get the first entry in the TShips. Or the second. Because MD has no access to its index. And without being able to iterate through the TShips there is no way to catalogue it.

So here comes the trickery. The way the engine differentiates typenames (SS_SH_A_TL for example, the Mammoth) is internally not really by reading the letters but by knowing at which index of the TShips that entry is, which results in every index position being represented by an integer. Thanks to Jack08, I got to know that the Mammoth, the entry at index '0' in the TShips (hence whenever a ship is not found in the TShips, a Mammoth is spawned instead, as it is the default to fall back on), is represented by the number '458752'. The typecode of the Mammoth. And it turns out that number can be used by the MD in this manner:

Code: Select all

{lookup.type.name@458752} ==> "Mammoth"

So. If the Mammoth is at index '0' in the TShips, and thus the first typename listed, thus it follows that

Code: Select all

[ typecode at index '0' ] + 1 == [ typecode at index '1' ]
And what do you know, that's actually correct. :)
Similarly, by using '{lookup.type@SS_SH_A_TL}' you'd get its typecode.

So, to sum up, we now have a way to iterate through all entries of the TShips (but theoretically also any other T-file, say, TAsteroids). What we need now is a way to know when we've reached the end of the TShips. Because turns out, if you try:

Code: Select all

{lookup.type.name@ [ last TShips entry ] + 1 }
Then you do get something back but it's not a ship. It's the text of a SE command. I don't quite remember which but it is something pretty odd to get back and I'm not sure it's the same one every time.
Anyway. One solution might be to simply define the last ship entry in the TShips and then not iterate any further. But that again requires hardcoding that we want to avoid. I've tested some more and ended up with this, which works:

Code: Select all

              <set_value name="CSTL.tempTypecode" exact="(458752+{value@CSTL.typecodeCounter})"/>


                <!-- This means that the end of the TShips has been reached. -->
                <do_when value="{lookup.type.class@{value@CSTL.tempTypecode}}" exact="lookup.type.class@{value@CSTL.tempTypecode}">
                  <set_value name="CSTL.endReached" exact="1"/>
The typecodeCounter variable is increased after every ship that is catalogued. If the variable endReached is set to '1', the iteration ends.
Turns out that when you try to get the class of a typecode that is not a ship, you get the MD code back, hence the check for 'lookup.type.class@{value@CSTL.tempTypecode}'.
Thus we have now found a way to check if we have gone through the entirety of the typenames to cancel the iteration, enabling us to completely automatize SRST.

There is more stuff that is part of replacing SRST but that goes beyond the idea of typecode traversing. It's also not completely finished or tested yet, but if you're interested you can find the current iteration of my SRST replacement, CSTL, here.
Note that it is part of a larger project of mine and hence relies on other files which are not included in this link.


Local Variables on Objects

This might not be that new or surprising to anyone that has worked with the MD before but using the engine ID of an object allows the easy setting of local variables.
Essentially, every object in game, that is, every single ship, station, but also "abstract" object like a sector is represented by a number for the engine. The same as for the typecodes for ship entries in the TShips. The way to get that number is very simple. You can get it by simply using '{object@this.yourObject}'. A "local variable" can be set by simply doing:

Code: Select all

<set_value name="{object@this.yourObject}_myValueName" exact="1"/>
That value is then treated as a global variable by the MD, which means ANY MD code can access it. Example use case: You have a MD script like LIFE by Shush which dynamically uses existing ships. But LIFE shouldn't activate on plot-related objects. For example, you don't want to get a dynamic mission from LIFE to go and protect the ship of your wingman in the plot, since he's getting attacked as part of the plot. To prevent that you could introduce a local variable called "plotObject". LIFE would then check whether the object it's considering to use has its local plotObject variable set to 1. If so, it ignores that object.
Bear in mind that I don't know how exactly Shush's LIFE script works or whether it would have that issue. It just presented itself as a good way to explain a use case. :)


MD Arrays

The MD doesn't have any proper support for arrays. The most it can do is groups of objects, but that's not suitable if you have a number of variables that you want to save in an array. But, hey, we can just make our own after all. :)

Code: Select all

<set_value name="this.arrayName_1" exact="10"/>
<set_value name="this.arrayName_2" exact="9"/>
<set_value name="this.arrayNameSize" exact="2"/>
There we go. That's it. Not especially complicated but it does work. You can also create them dynamically:

Code: Select all

<do_all counter="count" exact="{group.object.count@this.groupOfShipsInDifferentSectors}">
  <set_value name="this.sector_{counter@count}" exact="{group.object.{counter@count}.sector@this.groupOfShipsInDifferentSectors}"/>
<set_value name ="this.sectorSize" exact="{group.object.count@this.groupOfShipsInDifferentSectors}"/>
Because there is no way to create a group of sectors this kind of array is the only way I know of to keep tracks of multiple sectors in a "grouping" without saving them individually which is a pain.

Arrays in "proper" programming languages have several other functionalities though, which the MD obviously doesn't support since we just created those arrays out of normal variables. Thus it can be a bit complicated to implement different operations you'd want to carry out on arrays. So far the only thing I've had to implement for my purposes is a sorting library that allows me to sort the values inside an array. You can find the code for that here but again it's part of my larger project so it might not run on its own. It's a simple implementation of a bubble sort. Also, it's untested ingame but I'll get around to that and the adjustments to its code hopefully fairly soon.


Saving Strings

While it's possible to compare the name of an object to a string in a condition, that string in the condition is a constant. It cannot be variable. The only thing you could do is do a check like:

Code: Select all

<check_value value="{object.name@this.myShip}" exact="{9250,1}"/>
But that just shifts the constant to a text file instead, which can be useful but is not what we're looking for here.

So. The MD cannot save a string to a value. Thus, saving strings seems impossible. What it can do though is to change the name of an object. And read it. And the MD can also temporarily create an object where the player will never see it or even realize it exists. Hence, if we want to save a string in MD we can just write it into the object name of a beacon in a random sector 500km out with hidden="1". And we delete it once we don't need that string anymore. Unwieldy, I agree, but it works.
Might seem fairly useless but let's remember this: MD variables work inside of text IDs. Sector names are saved to text IDs. Sector name text IDs can contain MD variables. For example the variable '{object.name@this.myHiddenBeacon}'. And the SE can offer the player a text field to input a string. And the SE can also then set that string as the name of our little object, hence in theory renaming the sector. Poof. Player-named sectors. IF it works. Haven't tested it yet. ;)

In case you want to just generally save a string though, I wrote a small library for that. Once again though. Part of a larger project, yadda, yadda. ;)


Picking a Random Value from a Set Group of Values, and not Picking any Value Twice

This is a solution to a very, very specific problem I had and honestly might not be particularly useful to anyone: I wanted to have a group of values from which I want to pick one random value after another but not pick any value twice. There's probably also a better solution to this but the thing I've come up with is to just simply create a group of temporary objects, set their names to the values, then use '{group.object.random@this.myGroupOfTempObjects}' in conjunction with the 'remove_from_group' command until there are no more objects in the group. Not pretty for sure.


Variable Choice of Text IDs

I work a lot with variable text and such. Thus, depending on the occasion I want to get one text ID of a set or another. This can easily be achieved by a little bit of t-ID reference magic. This is used in vanilla MD code as well but still a very useful trick.

Code: Select all

  <pilot shipname="{9250,{value@this.value1}0{value@this.value2}"/>
This is a very easy application. Depending of the value of 'this.value1' and 'this.value2' different text IDs are being called upon. An example: Naming ships in a way that befits the race. If the ship's race is boron, this.value1 would be '2', for example, and this.value2 would be set to something random between 0 and 9. Hence the ship would use the text IDs from 200 to 209. If the ship was argon, this.value1 would be '1' and it would pull from 100 to 109. Simple stuff but allows for some awesome stuff. :)


"Outsourcing" Data to Text Files

Another little known about trick is this:

Code: Select all

<set_value name="this.value" list="{9250,1}"/>
In combination with:

Code: Select all

<t id="1">1|2|3</t>
This will set the value to 1, 2 or 3. It also works with other MD variables such as '{lookup.race@argon}|{lookup.race@boron}' in the text ID.
What it also allows is for an easy way for players to adjust values that the MD uses. For example, it would be possible to introduce a global mission reward multiplier that multiplies all mission rewards. By default it is set to '1' - none. But by "outsourcing" that variable value to a text ID, which is easily readable by a normal user, the user can then set that multiplier to '2' or '3' or whatever. :)

Similarly it would be possible to, for example, set up a MD file for compatibility with a number of major mods. And depending on whether a value in the text ID is set to, say, 'LU' or 'XRM', the internal code would adjust to be compatible to that major mod.


I may have more to post here in the future but this'll be it for now. :)
Last edited by enenra on Mon, 1. Feb 16, 10:11, edited 1 time in total.

User avatar
Posts: 1126
Joined: Wed, 16. Nov 11, 19:37

Post by RoverTX » Mon, 1. Feb 16, 08:34

Very nice presentation! I really appreciate this, and I am sure others do to. I knew before how to fake an array in the MD but this is the first clear explantation I have seen. I hope this get's stickied or at least linked somewhere so it doesn't get lost!

User avatar
Posts: 6174
Joined: Fri, 8. Apr 05, 19:09

Post by enenra » Fri, 5. Feb 16, 13:23

RoverTX wrote:Very nice presentation! I really appreciate this, and I am sure others do to. I knew before how to fake an array in the MD but this is the first clear explantation I have seen. I hope this get's stickied or at least linked somewhere so it doesn't get lost!
Thanks! I do what I can. :D

The topic has already been listed in the tutorials sticky, so I think that should cover it from that side. :)

By the way, if you (or anyone else) has other topics they're interested in in terms of MD (how to do something that can't be done with the stock commands) I might be able to give some further input about it. I can't guarantee I can help you do everything, in fact, a lot is just simply impossible without utilizing a SE link or just flat out impossible through the MD, but in some cases I might be able to think of something useful. :D

Lastly, I actually remembered two other things that I totally forgot I had come up with a solution for a while ago.

One of them is creating variable conversations to get rid of the need to create a new conversation for every single one you want to have in your mission. The other is to dynamically create a universe map (which I'm actually in the process of writing a MD script for). Don't have time to write those up now but will do so sometime soon. Oh, and I just remembered I also have this old library to fake in-space voice acting by abusing the tips popup system. :D

Post Reply

Return to “X³: Terran Conflict / Albion Prelude - Scripts and Modding”