Add an EP ship to SOTP tutorial

A place to discuss mods in development and concepts for new mods.
Post Reply
Militia Captain
Militia Captain
Posts: 850
Joined: Tue Nov 05, 2013 9:56 am

EDIT: Couple of minor edits on 29/5/19.

Note: this mod will only work if Eternity Port has been purchased and downloaded either from the Multiverse or Steam. There needs to be two files in the 'Collection' folder, 'EternityPort.tdb' and 'NearStarsVol01.tdb'.
You will find it very helpful to decompile 'Transcendence.tdb' and the two EP tdb files in the 'Collection' folder. Info on how to do that is here:
Also put the attached mod into your 'Extensions' folder and select it when you start a SOTP 'debug mode' game. How to run a debug game here:

Kaama requested a mod which would allow an Eternity Port (EP) ship to be available at Commonwealth dry dock Ship Brokers in Stars of The Pilgrim (SOTP or Part 1). A detailed explanation was also requested so everyone could learn a bit more about how to do this. Good thinking.
This mod will add a Eurasian Diarchy playership, the level 4 Demir gunship, to SOTP and allow it to be purchased at Commonwealth dry docks.

To do this we need to do a couple of things.
Firstly we need the ship code from EP. This isn't loaded when we play SOTP which is a separate adventure to EP, so we need code to make this available.
Secondly we need to change the code for the ship so the dry docks will sell it.
Continued in next post.
(1.27 KiB) Downloaded 51 times
Last edited by relanat on Wed May 29, 2019 5:46 am, edited 1 time in total.
Stupid code. Do what I want, not what I typed in!
Militia Captain
Militia Captain
Posts: 850
Joined: Tue Nov 05, 2013 9:56 am

EP ship code.
Beacuse EP and SOTP are different adventures they only use some of the same code. But a feature of the game called 'libraries' allows us to share some of the EP code with SOTP. This includes the playerships available at ship brokers, but doesn't include the starting playerships.

In our mod we will add the EP library &unidNearStarsVol01;. This library contains most of the EP code. By adding the library code to SOTP we make the game load all the code in that lbrary at game start. So the Demir playership code which is normally only available in EP will be available to us in SOTP.
Here's how.
This is the beginning of the mod:

Code: Select all

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE TranscendenceExtension
<!ENTITY unidD789EPAddDemir		"0xD7890000">
<!ENTITY evD789AddEPShips		"0xD7890001">

	;From Eternity Port.
<!ENTITY unidNearStarsVol01		"0x00910000">

		UNID=			"&unidD789EPAddEDShips;"
		apiVersion=		"44"
		name=		"Add EP Ships to SOTP"
		version=		"1.00"
		release=		"1.00"
		credits=		"relanat"
		extends=		"0x00200000"

<!--	Requires Version 1.8 (API 44) or higher.

		Kaama for the idea, and
		George for a great game.
	<Library UNID="&unidNearStarsVol01;"/>
It is a standard layout for an 'extension' mod. The first point we need to consider is on line 9.

Code: Select all

<!ENTITY unidNearStarsVol01		"0x00910000">
This is copied from the EP code. If we were playing an EP game then the computer would already know that 'unidNearStarsVol01 is the same as "0x00910000" because it has read this code from the EP files. Because we are playing an SOTP game the computer needs to be told this info. Otherwise we get an "Unknown UNID" error. So we include it here. We need this because of the code on lines 29 and 32 which we will get to shortly. (There is a bit more info on UNIDs here:

Also note on line 12 that is says '<TranscendenceExtension'. An 'Extension' is used to add features to a 'TranscendenceAdventure'. We will see an 'Adventure' example in the next section.

The next bit of code we need to look at is on line 19.

Code: Select all

This code tells the computer to only show this mod if we are playing SOTP. If you flick between SOTP and EP in the game select screen you will see the mod only appears as an option in the SOTP screen. But why does this happen?
'0x00200000' is the UNID for the SOTP adventure. If you search 'Transcendence_Source' for this hex number you will find it in 'StarsOfThePilgrim.xml'. It looks like this:

Code: Select all

<!ENTITY unidStarsOfThePilgrim		"0x00200000">
Scrolling down this file will show you:

Code: Select all

	UNID=		"&unidStarsOfThePilgrim;"
	name=	"Domina &amp; Oracus I: The Stars of the Pilgrim"
	release=	"1" 
Note the use of '<TranscendenceAdventure' here. We are writing a mod (Extension) which adds features to an 'Adventure'. By using the 'extends=' code we tell the mod which adventure we want the mod to run in. So this is a shortcut for the game to know when to show the mod for selection.

Next we come to the code that adds the playership code to the SOTP game. It is on line 28.

Code: Select all

<Library UNID="&unidNearStarsVol01;" optional="true"/>
This tells the computer to load the Near Stars library (this is the library's common name) which is part of EP.
If you open the file 'NearStarsVol01.xml' in 'NearStarsVol01_Source' in EP you will see a long list of ENTITY UNIDs. On line 626 we have the Demir playership, scDemirPlayerL4. This is the ship we are adding to SOTP. Just below that on line 637 we have the UNID for the Near Stars library. Here the code is for a '<TranscendenceLibrary'.

Code: Select all

<TranscendenceLibrary UNID="&unidNearStarsVol01;"
		name=		"Near Stars Vol 1"
		release=		"1"
Libraries don't do anything. Adventures are the games we play and Extensions are mods we use to add features to those adventures. Libraries are just a way of assembling all the information/code we need to play our games.
Inside the library code are a heap of module filenames. One of them, on line 675, is 'EDPlayerShips.xml'. If you open this file (use NotePad or NotePad++) you will see the <ShipClass UNID> for the Demir playership, &scDemirPlayerL4;.
So what has happened because of this line of code?
When we add a library to a mod like this the game looks for the library. If it finds it, it runs through all the ENTITY UNIDs and remembers them. Then it runs through any libraries inside that library (there are two in the Near Stars library, 'unidRPGLibrary' and 'unidHumanSpaceLibrary') and remembers all of that code. Then it runs through all of the modules that are listed and remembers all of the code in them.
So now we have a heap of the EP code available for use inside of SOTP. This includes the Demir playership as it is listed there and its code is in one of the modules.

The other part of this line of code is 'optional="true"'. This is a very handy bit of code which was introduced fairly recently. This tells the game not to worry if the Near Stars library isn't available. This library is only available if EP is in the 'Collection' folder. If EP isn't present then the computer does nothing but, more importantly, doesn't give an error. So we are covered either way. If EP is available the mod will add the Demir playership, but if EP isn't available the computer just does nothing.
Continued in next post.
Last edited by relanat on Wed May 29, 2019 5:45 am, edited 1 time in total.
Stupid code. Do what I want, not what I typed in!
Militia Captain
Militia Captain
Posts: 850
Joined: Tue Nov 05, 2013 9:56 am

OK. So now we have the Demir playership ready for use in SOTP. But because the Demir was designed to be used in EP the code is slightly different to playership code used in SOTP.
So we have to change its code so it will appear in Commonwealth dry docks for sale.
There are a couple of points to consider.
We need to make the ship compatible with the dry dock's ship selection criteria.
And we also need to consider which dry docks the ship will appear in.

Stations uses attributes from playerships to determine which ships are offered for sale.
In the dry dock UNID, '<StationType UNID="stCommonwealthDryDock"' (line 612 of 'Commonwealth.xml' in 'Transcendence_Source') we need to look at the <Encounter>, <Trade> and <Dockscreens> code.

We will take a quick look at the 'Trade' code first.
Inside the <Trade> code there is a line of code called <SellShip>.

Code: Select all

<SellShip	criteria="s -notForSale;" priceAdj="100"/>
The code here allows any ship to be sold at a dry dock except for ships which have the 'notForSale' attribute. Fortunately, none of the EP playerships have this attribute so we don't need to change anything here, but we did need to check.

Next we will check the <Dockscreens> code. Only the top part is shown here as it is only 'actionBuyShip' that affects this mod:

Code: Select all

			<Default descID="descWelcome">
					<Action id="actionBuyShip">
						(scrShowScreen gScreen &dsRPGShipBroker; {
							createCriteria: "s +systemLevel:0-2; +commonwealth; -military; -notStandard; +shipBroker;"
							checkMilitaryID: True
The action shown here appears as "Ship Broker" in the dockscreen of the dry docks. When we click on this action we go to the ship broker screen which shows the ships that the station has for sale,
We need to take a closer look at the 'createCriteria' line of code as this affects which ships will be offered for the player to buy.
The code has two main areas; the 'systemLevel' part and the attributes section after it.
We will start with the attributes:

Code: Select all

+commonwealth; -military; -notStandard; +shipBroker
This code checks the attributes in the ShipClass UNID to determine if that ship should be offered for sale at the dry dock.
If there is a '-' before an attribute here it means a ship will only appear if it doesn't have that attribute. So any ships which have the 'military' or 'notStandard' attribute in their code will never be offered for sale at a dry dock.
On the other hand a '+' before the attribute means that the ship must have these attributes in their code to appear for sale. So for a ship to appear at a dry dock it must have both the 'commonwealth' and 'shipBroker' attributes.

Let's check the code for the Demir playership. As mentioned above it is in 'EDPlayerShips.xml' in 'NearStarsVol01_Source'.

Code: Select all

<ShipClass UNID="&scDemirPlayerL4;"
	manufacturer=	"Severnaya Industries"
	class=			"Demir"
	type=			"gunship"
	defaultSovereign=	"&svEurasianDiarchy;"

	level=			"4"
	attributes=		"eurasianDiarchy, playerClass, shipBroker"
	inherit=		"&scDemir;"
There are three attributes listed here, 'eurasianDiarchy', 'playerClass' and 'shipBroker'.
Cheking back against the dry dock code; there isn't a 'military' or 'notStandard' attribute listed here so that is OK, and we have the necessary 'shipBroker' attribute, however there isn't a 'commonwealth' attribute listed so the current code will never allow the ship to be sold in a dry dock.
So we will need to add 'commonwealth' to the list so we can buy the Demir.
Separately, note that the Demir is a level 4 ship, 'level="4"'. We will need this later.
Continued in next post.
Stupid code. Do what I want, not what I typed in!
Militia Captain
Militia Captain
Posts: 850
Joined: Tue Nov 05, 2013 9:56 am

There are a couple of ways to add an attribute. In this mod we will use 'xml' functions. There are a couple of XML example topics on the forum. The best is by NMS and can be found here:
This is the second part of the mod code where we add the attribute to the Demir code:

Code: Select all

<Type UNID="&evD789AddDemirToSOTP;"
			(block Nil
				(if (and (not (isError (typGetXML &scDemirPlayerL4;)))
					(typGetXML &scDemirPlayerL4;)
					(block Nil
						(setq demirXML (typGetXML &scDemirPlayerL4;))
						(setq oldAttributes (xmlGetAttrib demirXML 'attributes))
						(setq newAttributes (cat oldAttributes ", commonwealth"))
						(xmlSetAttrib demirXML 'attributes newAttributes)
						(typCreate &scDemirPlayerL4; demirXML)
We are changing the code at the beginning of the game (before play starts) using the '<OnGlobalTypesInit>' event.
The game is quite fussy about altering code using 'xml' functions. So we use a few checks to make sure we don't do something that creates an error.
In line 31 we add a line of code which checks if the Near Stars library is available (EP is in the 'Collection' folder). This 'libraryCriteria' code will only let the code in this UNID run if the Near Stars library is available. The '+' before the library UNID means that this library must be available for the code to run.
Then in line 36 we check to make sure that we aren't creating an error by using 'isError'. We also check that the code for the Demir playership is available using 'typGetXML'.

OK. So the library is available, we aren't creating an error and the playership code is ready for us to alter. Now we run through the code needed to add the attribute. (Note: when we check this code in the debug console it has already run before the game started so the new code has already been set.)
On line 40 we get the playership code. This is all the code from the playership UNID starting at '<ShipClass UNID="&scDemirPlayerL4;"' and finshing at </ShipClass>. A total of 30 lines of code. We use the 'typGetXML' function for this and we save this code as 'demirXML'. We can check this in the debug console (called the console from now). Press 'F9' and the console will appear on the right hand side of the screen. Type in "demirXML" and press 'Enter'. As in the attached image the code for the Demir playership will be printed in the console.
It is however a bit difficult to read here. You might like to type in "(printTo 'log demirXML)" and press 'Enter'. This shows "True" in the console but also prints the code into the 'Debug.log' file in the Trancendence game folder. Double-click on the 'Debug.log' file and the code will be at the bottom of the file in a form which is a lot easier to read.

In line 41 we get the 'attributes' line of code from the playership code. We use 'xmlGetAttrib' to select this code and save it as 'oldAttributes'. These are the three original attributes from the Demir code (see image).

Next we add the new 'commonwealth' attribute. We use the 'cat' function. This adds the text inside the double quotes on line 41 to the 'oldAttributes' text so we now have a list of 4 attributes. Note the use of the comma before the word "commonwealth". All the attributes need to be separated by a comma. This list of four is called 'newAttributes'.

Now that we have added 'commonwealth' to the list we can put this list into the playership code. We use 'xmlSetAttrib' to add the 'newAttributes' to 'demirXML'. This overwrites the three original attributes with the 4 new ones.
Finally on line 33 we use 'typCreate'. This saves the new code so that the playership code now has 4 attributes.

Looking at the attached image we can see the 'commonwealth' attribute on the second line of the 'demirXML' code. So now we have a playership with the required code for it to appear in dry docks.

Lastly we need to check which systems the ship will be offered for sale in. We do this by looking at the dry dock's 'Encounter' code:

Code: Select all

	systemCriteria=		"+commonwealthSpace;"
	systemAffinity=		"+commonwealthCore;"
	levelFrequency=		"uucuu r---- ----- ----- -----"

	locationCriteria=		"+planetary"
	enemyExclusionRadius=	"75"

	unique=			"inSystem"
The 'levelFrequency' line of code determines which systems the dry docks will appear in. The funny-looking "uucuu r---- ----- ----- -----" code represents the 25 system levels in Transcendence. SOTP has systems from level 1 (Eridani) to level 10 (Heretic). These correspond to the first 10 values, 'uucuu r----'.
A dash, '-', means that a dry dock will never occur in a system of that level. So you will never see a dry dock in a level 7 or higher system.
A 'c' value, used here in level 3 systems, means the dry docks will occur with a 'common' frequency.
'u' is short for 'uncommon' which means dry docks are less likely to occur in this system level than a level 3 'common' system.
And 'r' means 'rare'. So a level 6 system is even less likely to have a dry dock in it than systems of level 1 to 5. The system level can be checked by looking at the code in 'HumanSpaceMap.xml' in 'Transcendence_Source' or by using the 'sysGetLevel' function.

If we refer back to the 'Dockscreens' code from the dry dock we can check the 'systemLevel' criteria for the ships. It was '+systemLevel:0-2'. This means the dry dock can offer ships for sale in a range from the system level up to 2 levels higher. So a dry dock in a level 2 system will offer ships of levels 2-4. A dry dock in a level 4 system can offer ships of level 4-6. A dry dock in a level 6 system can sell ships from level 6 to 8.

Because the Demir is a level 4 ship (see above) it will only be offered for sale in systems of level 2-4. In SOTP this is from system "C3" (two after Eridani) through Rigel and St Kats and up to "C5" (two after St Kats).

Additionally, the 'unique="inSystem"' code means there will only ever be one dry dock in a system, never two. This affects how often the Demir might be available for sale. Although there will only ever be a maximum of one dry dock in a system there is no guarantee that a dry dock will appear anywhere. So in a game without any dry docks the Demir will never be available to buy.
Also be aware that ship brokers are not guaranteed to offer any ship for sale. The code finds every ship that can be offered for sale by a particular station but then randomly selects only some of them to be available to buy.

Hope that helps. It is difficult to explain this using text only.
demir debug.JPG
demir debug.JPG (200.3 KiB) Viewed 1641 times
Last edited by relanat on Fri Jun 14, 2019 6:05 am, edited 1 time in total.
Stupid code. Do what I want, not what I typed in!
Militia Lieutenant
Militia Lieutenant
Posts: 226
Joined: Wed Feb 11, 2015 10:32 am
Location: Scouring Dantalion System for CSC Antarctica...

Wow, nice little tutorial there Relanat! :D I'm sure this will be very helpful to both beginner and more experienced modders alike! Good job!
Post Reply