(Thanks to BadRabbit and MightyOnion for writing this helpful guide!)
Program
In every game lies a set of instructions for your computer – the code. As you immerse yourself in the gaming experience, your computer masterfully assembles these instructions, weaving together an interactive world for you to explore. Each action, possibility, and outcome is thoughtfully accounted for within this intricate web of code.
Tools
The first step to doing anything behind the scenes is to be able to read those instructions. Download and install Visual Studio Code (from https://code.visualstudio.com) and download and install the twee 3 language extension (click on extensions on the visualstudio webpage and search for “twee”). Follow the instructions to install that extension.
The relevant language should show up in the bottom right of the screen. This gives you a tool to read the instructions with.
However, as a default the game is designed to be played not read – so it is in the form of an html file – which you will be able to read using VS Code but the next step will make it easier.
The next step is to change the game into a form where we can read it more easily. Download and install the latest version of Twine (from twinery.org). Do not use the online version. Go to Library>Import. From there you can import the game file. Open the game in the library and then select save as twee from the menu. This saves the game as a twee file – which can then be opened and read with VS Code.
You now should be able to use VS Code (sometimes called VS Studio) to read the game and any mods that you want to look at (which should be in twee or if they are in .zip or .xcl they need to be unzipped first and then you can read them).
Looking at the game
Now go ahead and open up the game.
So, double-click on the twee file that we created from Twinery. It should be called X Change Life. (It should have the .twee extension if you can see extension names). You may need to use the “Open with…” command to open the file with Visual Studio Code.
You should hopefully see something like this:
These are the instructions that the computer uses to make and display the game. If you look more closely you can see in the bottom right corner a reference to the relevant language – that should be twee followed by a number (at the time of writing this guide it was 3 but it might be higher).
Also if you look at the code you will see that it is divided into chunks called passages that all start with “:: “. If you are familiar with Twine this shouldn’t be a surprise to you.
The game displays the different passages that it is told to display when it is told to do that. If you are familiar with Twine you will realise that this is different to the way most Twine games work – which transfer from one passage to another – rather than merely displaying them – there is some more on this below.
What mods do
At its most basic a mod changes what happens when the computer looks at the instructions – all the different passages which are all preceded by “:: “. Generally, a mod puts in a new passage to replace an existing passage – that means that the game does what is in the new passage instead of what was in the existing passage.
Most basic mod
The first thing to do when implementing a mod will be to try to figure out what you want the mod to do and then where you want the change to go so that that happens.
The most straightforward mod will be to change the existing text to something different. We are now going to do that now.
First, we will need to open a new document to make a mod. In VS Code you can make a new file – in file>new file. At the top of the page you can decide what to call it – call it Mod1.twee – VS Code will prompt you to create the file somewhere – make sure that you create the file somewhere that you will be able to find it.
At the same time we want to open the X Change Life game file with VS Code. Once you have the game opened in VS Code you can use (ctrl-f) to find certain passages. For this mod we want to try to change the display at the very start of the game. When you start the game part of the instructions to the computer are to display the game version. Use ctrl-f with “:: game version” to get to this passage:
:: Game Version {"position":"0,0","size":"100,100"} <div class='bottom_right'>(color:"#e27087")[(display:"__mods__") X-Change™ Life Version 0.16f Updated February 12 2023]</div>
This is the version that is current at the time these instructions are written. The version number and updated date will likely be different when you come to look at it.
For this mod we want to select all of the passage (all the text in this window – starting at “::” and finishing at “</div>”, then copy it then change the screen or window to our mod and then paste the copied section in.
Now we are going to change the text in the mod. We will change the text for the Version – delete that word and in its place type something else. If you are really stuck you can literally type “Something else”. Now click save or ctrl-s. We have made our first mod! It should look like this:
:: Game Version {"position":"0,0","size":"100,100"} <div class='bottom_right'>(color:"#e27087")[(display:"_mods_") X-Change™ Life Something else 0.16f Updated February 12 2023]</div>
Now we want to apply that mod. Close VS Code and find where you created the Mod1.twee file. Once you find it, cut it and paste it into the “mods” file in the X Change Life installation. In the same folder you should also be able to see folders labelled “applied” and “disabled”.
Now you want to use the mod loader to change the game so that it will use our mod. These are current instructions that work on mod loader 1.1.2. Hopefully they will continue to work.
Start the mod loader. Highlight X Change Life – the game and click on “load mod”. The mod loader should now create a version of the modded version of the game with the word “Mod” at the end.
Next go to where the game is and run the modded version. Now on the start screen you should see something like this:
If you look carefully you will see that our mod has taken effect and instead of printing the original text in the bottom right corner the modded game has printed what we told it to print with our mod.
NOTE: Make sure you get the capitalisation of the passage name correct – passage names are case sensitive.
Conflicts between mods
A conflict occurs when more than one mod tries to change the same passage. So, for example, in our mod we changed the game version name to be something else. If a different mod changed the same passage (for example, if that other mod changed the date) then the two mods would be in conflict.
Passage tags
Passage tags are listed and explained here:
Amongst other things, passage tags stop mods being in conflict with each other.
Instead of needing to overwrite the original game to change things in the game at certain points the game itself checks to see if it should do something else – these are the parts of the game where passage tags are inserted.
This means that a mod may not need to over-write the original game. If the mod would change the bit of the passage where the passage tag is inserted then it can (and should) use a passage tag. So, for example, if you wanted to make a mod that allowed a character to talk to the barman at the bar you would need to change the way the game behaves when it offers the character options at the bar – the relevant passage is “:: bar options”. Without tags you would need to rewrite that passage to give the character the option. However, with passage tags instead of replacing the “:: bar options” passage you can just add the [bar_options] passage tag to your modded passage.
The game itself looks for things that it should do in addition to what it already is doing. This means, amongst other things, that more than one mod can insert an option – because the base game is not being changed from more than one source.
An alternative explanation from their author (McLurkington) is:
“So, you could try and edit the bar options passage (which is in /project/twee/places/bar/bar.twee on the repository), but thankfully there’s an easier way if you just want to add some new options. There’s a set of “passage tags” I set up that let you do that sort of thing. There’s a longer description of what these do in /project/twee/core/utility/passage tags.twee, but short version, you can ‘tag’ a passage with certain keywords, and the game will automatically insert whatever is in your passage in the appropriate place. So, for example, you could write something like this:
:: my bar option [bar_options] {
(link:"Play darts")[(set:$next to "your play darts passage")(display:"change screen")]
}
And that bar_options [tag] will add that snippet with a link to your passage in at the end of the normal bar options/buttons.”
Also McLurkington said specifically in relation to the (location)_exit tags:
The ideal way of setting up your passage using the (location)_exit passage would be something like this:
:: your exit passage [bar_exit]
{
(if:($today_events does not contain "<your event name>") and (<your condition to trigger event))[
(set:_exit to "false")
(set:$today_events to it + (a:<your event name>))
(set:$next to <your event passage>)
(display:"next") ] }
Our second mod
Now we will try to make a mod with passage tags.
One of the things that you might want to be able to do at the bar would be speak to the bar tender. This would mean that we want to add an option to the bar options passage. There is a passage tag there already so we can make a mod that won’t conflict with any others.
So the first thing to do would be to start VS Code again and create a new file. Call this one Mod2.twee
Then we can start making the mod. First we need a name for the passage.
Let’s call it
:: bartender chat
Remember all passages start with “:: “
Next, because we want it to go in as a bar option we need to attach the bar options passage tag, so;
:: bartender chat [bar_options]
This tells the computer to look at this passage when it reaches the part of the game where it gets to the bar options passage. So far the computer will look at the passage but it’s empty so it won’t do anything. What we want is for it to do something.
We will base our passage on what McLurkington has suggested so we will type in:
(link:"Chat with the bartender")[(set:$next to "bartender chat 2")(display:change screen")]
Now we are telling the computer to do something when it looks about for passage tags and sees our tagged passage. We have told the computer to make and display a “link” and to call it “Chat with the bartender”. That is the part in (). If the link is clicked on then the computer will do the part in []. What actually happens internally is very complicated. All we need to know for today is that the middle part of the screen will be changed to display whatever is in the “bartender chat 2” passage.
We don’t yet have a “bartender chat 2” passage so we need to make one now. Type in:
:: bartender chat 2
You will see that this doesn’t have a passage tag – we already got the attention of the computer with our earlier passage – we don’t need to again. Now there are two things that we need to do (1) do whatever we want the mod to do and (2) get back to the main game.
I am feeling fairly uninspired so we will just type in:
You chat inconsequentially with the bar tender until he has to go and attend to other customers.
This is what the mod will do (1). Now what we need to do is get back to the main game somehow(2).
We came here from the bar options passage. Realistically, the mod hasn’t changed anything in game so we can just go back to where we came from. So, type in:
(link:"Finish")[(set:$next to "bar options")(display:"change screen")]
And that is your second mod complete. It should look like this:
:: bartender chat [bar_options]
(link:"Chat with the bartender")[(set:$next to "bartender chat 2")(display:"change screen")]
:: bartender chat 2
You chat inconsequentially with the bar tender until he has to go and attend to other customers.
(link:"Finish")[(set:$next to "bar options")(display:"change screen")]
Again, you now need to apply it and play the game – so do what we did above – find your mod and paste it into the mods folder – then load it using the mod loader. Now you can play the modded game. After you start the game at some stage choose to go to the bar and choose chat to the bartender as an option. You can then return to the bar after chatting with him.
A possibly unexpected consequence is that the picture in the middle of the screen disappears. We may want to change this. To change it for when we return from our mod, we can return to “Hit the bar” (the main screen of which the “bar options” passage is only part). Also, because “Hit the bar” is a passage the fills in all three parts of the screen we need to use “next” instead of “change screen” (as explained more below).
So we want to change :: bartender chat 2 to read
:: bartender chat 2
You chat inconsequentially with the bar tender until he has to go and attend to other customers. (link:"Finish")[(set:$next to "Hit the bar")(display:"next")]
Once you have done that save your changes.
Again, you now need to apply it and play the game – so do what we did above – find your mod and paste it into the mods folder – then load it using the mod loader. Now you can play the modded game. After you start the game at some stage choose to go to the bar and choose chat to the bartender as an option. You can then return to the bar after chatting with him. Now when we return from our mod to the main game we see the screenshot of the bar.
There’s still no picture when we are actually talking to the bar tender though. Looking through the code for the passage “:: Hit the bar” we can see the code:
(print:"<img class='grayborder' src='img/places/bar/wide.jpg' width=100% height=auto>")
That seems to be printing (on screen) a picture of the bar. Maybe if we put that in our mod then our mod will print a picture of the bar. Let’s try that.
So we copy that part from “:: Hit the bar” or type it out. So the mod now reads:
:: bartender chat [bar_options] (link:"Chat with the bartender")[(set:$next to "bartender chat 2")(display:"change screen")]
:: bartender chat 2
(print:<img class='grayborder' src=img/places/bar/wide.jpg' width=100% height=auto>")
You chat inconsequentially with the bar tender until he has to go and attend to other customers.
(link:"Finish")[(set:$next to "Hit the bar")(display:"next")]
Again, once we’ve done typed out what we want, we need to save it, apply it and then play the modded game. Now, hopefully, it should work as we might expect. We can go to the bar, talk to the bar tender and go back to the bar.
NB “Hit the bar” starts with a capital letter.
Notes
A lot of people may talk about Harlowe, being the language the game is written in.
You can find a guide for how to use Harlowe here: https://twine2.neocities.org/
Although the guide is helpful it is important to realise that this game does not always use Twine in the same way that the majority of games use Twine. In particular the transition from one passage to another is handled very differently. This means that some commands e.g. (history) that might be fairly helpful in other games are of limited utility in the present game.
I find the explanation from McLurkington to be very helpful it was:
“There are a few special passages that are the way you link to most things in X-Change Life. The passage “change screen” replaces just the main content area of the screen (the big middle column) with the contents of whatever passage is named in the variable $next. So you’ll see a lot of places where there’s something like this: (link:”Continue”)[(set:$next to “some passage name”)(display:”change screen”)]
What that does is make a button, Continue that the player can click, and when they do it sets $next to the name of the passage that should be loaded (set:$next to “some passage name”), then replaces everything in the center column with the new content (display:”change screen”).
There’s a similar passage named “next” that replaces all three columns instead of just the center column. That one is mostly just used for situations where you’re changing to an entirely new location or scene and you want to switch the sidebars up somehow.
And then there’s sort of a third one, advance time, which you use when the player is leaving a location and you want to move to whatever the next time period is. Like, when you leave the mall, the game uses advance time to move ahead to whatever activity you selected for that night.
(You don’t really have a lot of control over where that last one goes, it just pulls whatever activity the player selected for that time period.)”
We have already have experience of this with using “next” instead of “change screen” when we used “Hit the bar”.
Sometimes “next” will work and “change screen” won’t. The reason for that was explained by McLurkington as:
(display:”next”) clears the entire passage, then loads whatever passage is named in the variable $next. (display:”change screen”) just replaces the contents of the ?screen named hook, which is typically the center column/main body of the page. But ?screen isn’t something that is automatically always in place. When you use (display:”next”) you have to intentionally include it in whatever passage you are calling. You’ll see a lot of places in the core game that have some variation on this:
[(display:"character status")]<status|
<div class='center_screen' data-simplebar>
[]<screen|
</div>
<div class='top_right' data-simplebar>
[(display:"location and time")]<right_screen|
</div>
The specifics will change, and most of the time there’s a bunch of stuff inside the []<screen| codehook… but this is the basic framework of how the page is laid out. If that ?screen named hook is missing, then (display:”change screen”) won’t have anything to replace, and you’ll just see nothing or get a blank page.
So most of the time, you want to set up your mod in one of two ways, depending on what you are doing. If you are adding a short snippet, or just expanding on something already in the game, I’d suggest just using (display:”change screen”) to replace the body of the page the whole way through. That way the normal framework of div elements and named hooks will all be in place already and you won’t have to worry about it. If you are adding a longer scene, want to change something about the normal page layout, or something similar… then what you want to do is start your scene/location using (display:”next”) to show a passage that sets up that page framework, and then after that, you can use “change screen” to load the rest of your content in.
Images
In relation to images:
Aphrodite said:
“For those who are curious, I use Prequel’s AI “Cartoon+” filter on images to get the cartoon look for images. Prequel is also weirdly expensive. It works best on high quality images and often needs post tweaking. From there I use Topaz Sharpen AI to polish up the images and make them more game-ready, since Prequel produces fairly low res looking stuff. For videos I use the Clip2Comic app, with a few specific parameters. I turned “lines” all the way off, I turn “blackness” all the way off, and “detail” all the way up. Prequel can also do videos but it only looks good on select ones. Portrait photos are 540×960, landscape photos 960×540, square photos 540×540.”
The game uses the mp4 format, exclusively.
Audio
The game uses the Harlowe Audio Library. Information about that can be found here:
https://twinelab.net/harlowe-audio/#/
The game uses the mp3 format, exclusively.
Metadata
By default mods are loaded in alphabetical order and if they overwrite the same passage the mod loader displays a message indicating that fact. You can use metadata in mods to control their interactions with each other.
Information about metadata can be found here:
https://gitgud.io/xchange-life/mod-loader/-/blob/main/metadata_info.md
Aspect-oriented programming
Version 1.1.4 of the mod loader contains a new feature for mods: Aspect-oriented programming (AOP). If you are a programmer you may already be familiar with this. The basic idea is that a mod can define advice that “modifies” a passage that already exists in the game, without having to copy that entire passage. Say for example the game contains the following passage:
<!-- Base game -->
:: chicken
Why did the chicken cross the road?
Now we want to create a mod to add the answer. Previously, we would have to copy the entire passage:
<!-- old-mod.twee -->
:: chicken
Why did the chicken cross the road?
To get to the other side!
This has two problems: It can be annoying to update when a new game version releases, and it can lead to conflicts with other mods that modify the same passage. Instead, we can define an around advice (the only type of advice supported at the moment) to modify the passage:
<!-- new-mod.twee -->
:: chicken [around]
(display:_around)
To get to the other side!
With the mod loaded, when the “chicken” passage is displayed, it’s the modded passage that is displayed. The mod passage then in turn uses (display:_around)
to display the original passage, before adding its own text. In effect, the mod has “modified” the original passage and appended its own text.
(display:_around)
can be used in more complex scenarios too. For example, a mod could only show the original passage conditionally: (if:some_condition)[
(display:_around)
]
When applying advice, the mod loader will produce the following code internally from the base game and new-mod.twee samples given above:
:: chicken-aop-somerandomstring
Why did the chicken cross the road?
:: chicken
(set:_around to "chicken-aop-somerandomstring")
(display:_around)
To get to the other side!
Multiple mods can apply advice to the same passage without issue. (display:_around)
in one mod’s advice will display the advice of the second mod, and the second advice’s (display:_around)
will then call the original passage.
I (salad/salaink) hope that modders can use this technique to modify some passages less intrusively, without copying. This should make it easier to update mods to new versions of the base game, and can improve compatibility between mods. If you have other ideas or know other common patterns that could use similar “deconfliction”, please suggest them on discord!
Random Tips
Look at existing code or mods – they will give you an idea of what can be done and how to do it.
If you are looking at mods you may come across the .xcl file type. As noted above, .xcl files are re-named .zip files. These are used to try to make using the mod loader easier. The mod loader uses zipped up .zip files. A lot of people were unzipping the zip files and then the mod loader was not loading those mods properly. To prevent this there is now the option to re-name .zip files as .xcl files so that they can be used by the mod loader but people (hopefully) won’t unzip them. If you want to look at a mod in .xcl format you can just unzip it using any zip utility (because it’s actually just a .zip file with a different name). If you are having problems it may help to rename the file .zip instead of .xcl
If you are trying to do anything with audio it is a good idea to check the game settings in relation to audio – there are three settings – if you limit what the game plays then it will be very difficult to test your mod. I would recommend choosing the play all sounds option, at least when testing your mod.
In relation to audio, the $sex_loop variable will usually re-set to “0” when the passage changes. If you want it to continue in the next passage then you can set the variable $sex_loop_continue to “true”. If you are doing this, remember that you will need to stop the $sex_loop at some point.
It is important to realise that passage tags can be used in a range of circumstances for a number of different effects. Before changing parts of the base game you should look at the list of passage tags and see if you can use one of them.
When playing the game use the ` key to open up a special window – you can use it to set variables and check their values. This can be useful if you want to see how the game works in particular parts or ensure that your mod is working the way that it should be. Also, when playing the game use the 1 key to open up debug mode. For what it’s worth, I find this really confusing and not actually very helpful though YMMV.
When looking at the code you will see a lot of (text)[text]. The bit in the () is called a macro and the bit in the [] is called a hook. It may be helpful to think of them as (condition)[action]
{} are special brackets to do with collapsing extra spaces (“whitespace”). They make things look neater but shouldn’t actually do anything.
Similarly, Harlowe does not require the same sort of line and spacing discipline that features in other programming languages, for example, RenPy.
Searching the discord channel can sometimes provide answers.
At the moment undefined variables default to “0”. So this means if you tell the game to check $apples and you haven’t already told it what value $apples is the game will use the value “0”.
When you make a mod (beyond what is in this guide) we would appreciate if you shared it. There is a mod releases channel on the Discord where people typically announce their mods – though access to that channel is restricted so you will need to ask someone for permission to post there. Almost all mods are now hosted on Lovers Lab (https://www.loverslab.com/ specifically https://www.loverslab.com/files/category/319-x-change-life/). After announcing your mod I would suggest that you go there to upload your mod.
Finally, if there is anything that you think is missing from this guide or that you found useful or helpful that is not mentioned please let us know so we can improve this guide (messaging us on Discord is probably best).
Useful websites
twinery.org
https://twinelab.net/harlowe-audio/#/
https://gitgud.io/xchange-life/xchange-life/-/blob/main/project/twee/core/utility/passage%2