This post is about the process of implementing scripted dialog content into Starsector. It’ll get technical toward the end and I’ll do a tutorial on how to implement a new piece of character interaction.
So! I’ve been given to understand that rules.csv is notorious among modders of the game – how unfortunate!
In response, allow me to present a spirited defense of this data file and its attendant content pipeline. I’ll talk a bit about what it is, why Alex made it the way it is, how I came to use it, and how it can be used in general.
(It’s probably important to explain what the heck a rules.csv is in the first place, isn’t it.)
The rules.csv file is the primary, but not only, means of creating dialog interactions within the game Starsector. This includes almost all situations where the player engages with an entity – a planet, fleet, or character – where a textbox pops up and allows the player to choose from a set of responses. After choosing a response, stuff happens, more text is displayed, and a new set of possible responses is presented. This interaction loop proceeds until the dialog is closed.
This system is the basis for conversations with NPCs, for most interactions with campaign objects (planets, derelicts, sensor arrays, jump-points), and nearly all of the missions and story events.
In short, rules.csv contains the data used by what amounts to a custom scripting language that hooks into the game code to drive almost all content which doesn’t involve the combat map or the campaign map.
It’s a big deal!
Though some may find it hard to believe, I do enjoy using rules.csv because, above all, it’s lightweight. Every step is fast, simple, and responsive. I’ll walk through how this looks in practice in the tutorial section of this post.
First, though, I’ll get into how Alex came to build this system in the first place and how I came to use it.
How Did This Even Happen
Alex watched this video of Elan Ruskin talking about implementing an interactive content system at Valve. Then he made the system described therein.
(Note: the “AI” in the video title refers to making responsive content for non-player characters in video games, it has nothing to do with the recent hype-wave about using large datasets to generate text and images.)
(Also, props for the Verner Vinge callout at 21:51.)
The video lays out the entire theoretical and practical basis for the implementation the Starsector rules system, and explains why this kind of system can work well – as it does for us – to empower writers (me!) to quickly create and iterate game content. It’s not just a purely technical matter, but one of workflow and project management: who can do what, who can make decisions and where.
In other words, the content creation process is not (only) made for the mindset and convenience of the implementing programmer, but with consideration for the content creator(s) as well. It’s always been my experience in game development that when non-core programmers are given content creation tools which empower them they’ll make shockingly cool things of quality which is often well beyond the expectations of the coder who created the tools!
(Of particular note in the video, I appreciate the point about how he spent a bunch of time making a graphical interface for writing dialog – writers, they’re artsy people right? They must like visuals right? – and the writer he was working with hated it. I share the sentiment; I can’t stand visual programming interfaces! It feels like a layer of mush set between me and what’s actually happening. I remember taking a programming class in art school and being encouraged to use a visual programming tool to learn to code, and I was like “No! I want to use C!”)
The rules systems is only part of it, of course.
The real story is that Alex slowly accreted features into Starsector to expand upon the original combat sim. Alongside the new features – the non-combat campaign layer, planets, characters, missions – Alex also added an increasing ability for me to collaborate more directly on game content. And, well, that story is contained in the last ten years of blog posts… which you can view on the right side of your screen.
But to give an example of how this process developed:
For narrative missions in particular, we started with a process that saw me write dialog in a shared Google doc/sheet. Alex would implement what I’d written from the doc, and this is how we made the “Red Planet” and “Scientist AI Core Bar Event” missions.
We realized that this process was a bit of a chore, especially if I wanted to make edits or add additional content without having to go through Alex. And besides, assembling dialog text in code has a lot of Java markup around it that makes it difficult to know how the text will feel in-game, and it makes it difficult to catch small errors. Just the other day I found an incorrectly uncapitalized letter in one of the hardcoded dialogs!
If I recall correctly, the rules.csv system did exist at this time, but was used mostly to handle generic interactions with markets, and… things. We did not have the backend to (relatively) easily handle actual missions with stages and associated intel UI features – which is why Alex was one-off coding the things. Nor was the set of functions which rules.csv could draw upon for conditions and triggers terribly expansive; certainly all the code hooks existed to do almost anything you could imagine, but the rules parser was not set up to use them. Doing something slightly special such as (say) hiding or unhiding a character in the comms directory would require custom code rather than a simple call in rules. (I use this example because I think this is one of the first customs rules script hooks that I implemented myself! Yes, I was very proud.)
But there was the problem: while Alex could write custom code to do anything at all, but I couldn’t; not without making a giant mess, anyway. I went to art school not computer science school!
Anyway, Alex built general-use structures for missions and steadily improved, well, everything. Meanwhile, I had to learn to use this stuff.
My approach was generally to look at what he’d already implemented, copy a piece of content, then modify certain elements to create a variation that did something a little differently. Sometimes this worked! Sometimes I’d come shamefully crawling back to Alex and have to say “Sorry, I exploded the game again.”
(Alex is extremely patient. I have no idea how he does it.)
I recall now two of the first rules interactions I wrote: one was for flying through derelict Gates and, spookily!, nothing happens. The other was visiting the Beholder Shrine and getting a text dump; Alex then added the bit about donating supplies after we talked about how much fun it was to actually be able to visit a shrine. (Later, this whole line of thought exploded outward into the mission to visit all of the Luddic shrines.)
![]()
Stepping back (in time), I particularly got more into scripting/coding bits and pieces for Starsector after Gaslamp Games closed down in 2017 – not only, obviously, did I have a lot more time to work on Starsector, but I was also coming off a period of doing heavy Lua scripting for Clockwork Empires gameplay. This involved creating game objects and quests structured not so unlike the way Starsector’s missions were eventually structured (due to these being commonly solved design pattern in games, nothing more; I wasn’t the code architect for either game.)
Not only that, post-Gaslamp (and, to be honest, immediately pre-post-Gaslamp) I got into learning the Unity engine – yes, hindsight is 20/20 – and with it, the C# programming language. C# is basically Java, so it made learning Starsector code far easier because I had a place outside of Starsector development to screw around and learn by making lots and lots of mistakes.
And as I’ve learned to use both Java and rules.csv, I’ve been able to add increasingly complex narrative structures into Starsector – best of all, it’s super fun! (It doesn’t happen often, but especially rewarding is surprising Alex by using his game to do something he hadn’t thought possible.)
Let’s talk about how the magic happens.
And by magic, I of course mean rules.csv.
Note: this tutorials gets very technical and is probably only of interest to modders or people interested in boring implementation details. If you want fun updates on Starsector content, skip to the design comments section at the very end of the post.
A Rules Tutorial
I’m going to walk you through how to implement some new dialog.
Maybe this will be of interest to regular players, a little peek behind the curtain to see how things work.
Maybe it’ll inspire someone to get into modding? I can only hope. I’m going to write this from my perspective, by the way, not from that of adding a mod to the game – I, um, actually have no idea to do that off the dome.
Happily, someone else does.
A note on tools: I find Google Sheets to be my ideal method of editing lots of interconnected dialog pieces. It allows real-time collaboration between myself and Alex… and it works very nicely across several large monitors so I can pretty much display everything I need to take in a context surrounding a piece of dialog, everywhere, all at once. Though even that is not enough for some of the most complex interactions – if you’ve tried to read through the rules behind “The Usurpers” you’ll know what I mean – but it gets easier with practice and planning.
(I did, by the way, have a quick look at a rules.csv tutorial on the Fandom wiki and was shocked to see a link to Alex’s ancient rules documentation. This thing goes back to the very first doc I remember Alex sharing with me, and I don’t think it’ll upset him too much if I describe it as “scant”.)
Example Dialog Implementation – Supplies, I beg of you!
Let’s start simple: we’ll make a dialog option to allow the player to ask a planet’s quartermaster for free supplies.
To start, I’ll pull up the game and look at where the dialog I have in mind will appear.
(Note that I will be in dev mode; this is a boolean you can set in the settings.json file.)
I zip over to Jangala and find the quartermaster for a chat. Here’s our man, Min Chen:
![]()
He doesn’t have a lot to say to us right now. We’ll fix that.
Let’s hit the >> (dev) dump memory option.
![]()
This outputs a huge mass of text. This text is everything in memory accessible to the node of dialog we’re in while talking to Min Chen. (If you watched the Valve video, you’re going to start seeing the pieces come together very quickly.)
Each line starts with a name with a dollar sign in front of it, like $name. This is the id or key for a memory entry.
If this name has a dot in it, like $name1.somethingElse, this just means that name1 is a container which holds other memory values, one of which is somethingElse. You can think of them almost like files in folders in a filesystem.
Because we’re talking to Min Chen, we are currently “inside” his memory container, so if we see a memory key without a dot in it, that means the memory is attached to him. (Or it’s been put into the local context automatically for convenience. Don’t worry about it.)
If we want to access a memory within the planet of Jangala while we’re talking to Min Chen, we can call $entity.coolJangalaFacts. Separate memory containers exist for the planet of Jangala (the on-map planet entity), the space station orbiting Jangala (which is a separate object on the map!), the market attached to Jangala ($market, an entity that both the planet and station can access so you can trade and talk to people by moving your fleet to Jangala Station or the planet itself).
There’s also a $faction container, which in this case is the Hegemony because they own Jangala’s market. $personFaction is attached to Min Chen himself, though in this case it is the same as $faction because both Min Chen and Jangala are Hegemony. We’ve also got a $player container to hold information about the player and a $global container to hold information we want to access from anywhere in the game. (For the purposes of player-facing dialog, $player and $global are pretty similar because the person playing the game is always the $player and always has access to $global. …Don’t worry about it.)
Remember all that? Fantastic.
(… there are also memory values which can be called upon which aren’t shown in the memory dump, plus a bunch of special cases. Don’t worry about those right now either. The world is a complex, scary place enough already.)
Back to the memory text dump: after each memory name is an equals sign followed by a value. So (for instance), $entity.name = Jangala means the value with the key “name” is the string “Jangala”. In other words, the entity we’re interacting with is named Jangala. If you read through the memories in the screenshot, you can see that Jangala has a market, a station, doesn’t exist in hyperspace, and other neat #JangalaFacts.
There’s a ton of useful data here we can pull into dialog and use – not just in the $entity container, but also in $player, and of course in the current active character, Min Chen.
Writing A Rule, Finally
Let’s add an option to ask Min Chen for free supplies. This will require a new rule. I’ll open up the rules spreadsheet and add a new rule at the very end of our Rules spreadsheet, line 8182., which looks – to me – a like this:
![]()
(Click on the image to view it full size. I’ll make the rest of these a little smaller.)
I’ll explain what each column means.
id
A unique name for the rule. Every id must be unique.
(If you put a “#” character at the start of a line in a cell, that makes the line a comment and will be ignored by the rules parser. Put a # at the start of the id block to quickly comment out the entire rule.)
trigger
The name of the command that must be called to make the rule happen. This is only ever one string.
Some trigger names are set by convention and used throughout the rules file, ie. “PopulateOptions” is a generic call to open the starting options for dialogs initiated by the player. We want this new option to appear when we first start talking to Min Chen, so this is an appropriate place to use this trigger.
conditions
Describes a set of conditional restrictions on the trigger.
If we wrote “$market.id == jangala” as a condition (note the two equal signs), this would mean that the trigger would only fire if the player is at Jangala Station or the planet of Jangala. If we wrote “$entity.id == jangala”, then we would restrict the rule to trigger only if the player was interacting with the planet Jangala.In this case, we only want to fire this rule of the player is talking to the quartermaster, so we use “$postId == supplyOfficer”.
(Every character has a postID, and Min Chen’s is “supplyOfficer”. “supplyOfficer” is a back-end name for a character job which in the Hegemony faction gets displayed as “Quartermaster”.)
$isPerson is technically redundant because $postID == supplyOfficer is only ever attached to characters who also fulfill $isPerson. However, it is a good idea to put conditions which are simpler to query and/or are more restrictive at the top of the condition block for performance reasons. $isPerson is very simple, so it may be appropriate to use it here.
script
Allows a rule to fire off a series of pre-programmed commands with custom arguments. These are references to Java methods that have been set up so that rules.csv can call them.
The scripts section lets us do tons of cool stuff but we don’t need any of that yet so we’ll leave it blank.
text
Simply for text which is sent to be displayed in the in-game dialog box.
The rule we’re making is only to add a new option for the player to ask for supplies, so this can be blank.
options
Places responses at the bottom of the dialog box, allowing the player to choose how they wish to respond to a situation. The format for these is [key]:[text].
(Responses will be placed at the bottom of the dialog box in the order that rules create them, unless you override that ordering. We won’t worry about that now.)
A response line in options automatically hooks into trigger and conditions: when clicked, it will fire a trigger called “DialogOptionSelected” and populate into local memory an entry named $option with a value of the key string.
So if I want this response option to trigger another rule (and not simply cause an error), I need to add a new rule that checks the $option condition after DialogOptionSelected is called:
![]()
Note that the condition $option == bqfs_askForSupplies matches the key in the option line bqfs_askForSupplies:”Could you give me some free supplies?”.
All response options populated to dialog must use a unique key. If you want two responses to lead to the same option, you have to make a fake option that triggers the same-named option, like so:![]()
Assigning a new value to $option and firing DialogOptionSelected is essentially identical to the player clicking on an option with that key. It is executed immediately, however, so it looks like the text transition is instant. (Later, the player will read through rules.csv and realize that the game ignored their unique response and then get mad at me for the fake-out.)
… Anyway, I’m getting ahead of myself.
notes
For comments, not used by the game engine.
We’re only halfway there: the rule triggers, but does nothing (and still causes an error). We need to fill in some text to tell the player what’s happening, add a line to the script section to give supplies, and then figure out how the player gets back to the dialog flow.
Go back to the rule entry for bqfsAskForSupplies.
The script cell will get this:
AddRemoveCommodity supplies 10 true
AddRemoveCommodity is the command. Everything that follows is turned into arguments passed into the Java method written to handle the command by that name. I don’t remember what the “true” does, so I’ll just look at the code real quick… OK, it sets whether text for the commodities getting added (or removed) is automatically inserted into the dialog box. Default behaviour is to add the text, so the true is redundant, we’d only use that argument if we wanted to set it to false; I’ll remove it.
The text cell will get this:
"Well," $heOrShe says slowly, looking troubled. "I suppose we could spare a few supplies for an honest-looking spacer such as yourself."
This quartermaster is clearly a poor judge of character.
We’re still not quite done: if we leave it here, this rule just dead-ends the dialog and creates an error. The game will put a warning and allow the player to back out of the dead-end, but we really do need a way for the player to return to the dialog loop. How to do that?
You already know the answer: we need to call the trigger PopulateOptions!
So, in the next line of the script cell, after the command to AddRemoveCommodity, we’ll add this:
FireAll PopulateOptions
This will query and fire all rules with the trigger set to PopulateOptions. (If we used “FireBest” instead, this would only trigger the rule with the highest number of qualifying conditions. If you watched the Valve video, you’ll know what this means already, but before we get even more ahead of ourselves let’s see how these two rules work in-game.
To review, here’s the rules spreadsheet after the additions I’ve described:
![]()
The First Test
In Google sheets I go to File > Download > Comma Separated Value (.csv) and get the csv file. I open this in Notepad, select all, then paste into the master rules.csv file in the Starsector build folder – filtering the text through Notepad removes certain potential formatting disagreements.
I still have the game open, remember!
Starsector will hot-reload the new rules.csv, which is *great* because I can test my new rule without even leaving Jangala. Let’s “Cut the comm link” with Min Chen then restart the conversation and see what it looks like:
![]()
A new option appears! Fantastic. Let’s mash that a few times.
![]()
Awesome, free supplies!
But you see the problem: we can’t simply give the player free supplies again and again, forever. Such charity should come with… conditions.
Setting A Memory Flag And Using It
Let’s have Min Chen remember that he gave you supplies.
If you ask for more supplies without waiting (say) three days, he should deny your request.
To do this, we need to set a flag in Min Chen’s memory when he gives you supplies. In the bqfsAskForSupplies rule we’ll add a line to the script cell so it looks like this:
AddRemoveCommodity supplies 10
$gavePlayerFreeSupplies = true
FireAll PopulateOptions
A memory flag called $gavePlayerFreeSupplies will now be set to true when Min Chen gives the player supplies.
This is good, but Min Chen needs to actually give a different response when this memory value is set. (Note that the memory flag is set before calling Populate Options because we want to set this flag before triggering any more dialog rules – they might need to use this flag to evaluate conditions!)
Let’s make a second rule that fires as an alternative to bqfsAskForSupplies when $gavePlayerFreeSupplies is true.
Make a copy of the entire bqfsAskForSupplies rule by copying the whole row in the spreadsheet. This copy will need a new, unique name, so let’s call it bqfsAskForSuppliesDenied.
We’ll add a new line to this rule’s conditions cell:
$gavePlayerFreeSupplies
This is the same as writing “$gavePlayerFreeSupplies == true” (and, indeed, validates if the flag is any non-null, non-false value).
Remember also that this is set in the local context. In other words, this value is attached to Min Chen and only exists in his memory container.
If we set this value on Min Chen then fly over to Eventide and ask the quartermaster there for supplies, they will not know that Min Chen already gave you supplies, so you’ll get another 10 supplies!
Alternatively, we could set (and check) $faction.gavePlayerFreeSupplies if we wanted the entire Hegemony faction to remember it gave the player supplies. Or we could set (and check) $player.gavePlayerFreeSupplies to have the memory follow the player around wherever they go.
Anyway, I wrote a bit of text, put FireAll PopulateOptions to the script block rule, and now the new set of rules looks like this:
![]()
Let’s drop this new rules.csv into the game folder and see what happens when we talk to Min Chen.
(My old dialog from before is still in there, remember. The new interpretation of rules is output at the bottom of the dialog box.)
![]()
He knows.
Making Min Chen Forgetful
Let’s make this more interesting.
What if there was a time limit, and if you wait long enough, Min Chen will forget about giving you supplies and give you more? Easy: add an expiration to the memory flag by added an integer on the end of the line where you set the memory.
$gavePlayerFreeSupplies = true
becomes
$gavePlayerFreeSupplies = true 3
This number is a count of game-days until the memory expires.
You could also remove memories in a rule’s script block by calling unset, like so:
unset $gavePlayerFreeSupplies
But for now we’ll let the expiration timer do the work.
BTW Pronouns
You may have noticed the use of “$heOrShe” and “$playerName” in the text in the text cell. These are tokens which are automatically replaced with a memory value of the same name if it exists. A full set of these is automatically populated as appropriate for pronouns for character entities. Take note of the difference between $HeOrShe and $heOrShe – we control use of capitalization. Also, $himOrHerself will change to himself or herself, and so on. And of course there is $brotherOrSister for use by Luddics.
The player also has a few tokens which are automatically available in all contexts: $playerSirOrMadam will insert “sir”, “ma’am”, or “captain” (the three genders) as appropriate.
You could even put $gavePlayerFreeSupplies into the text cell and it would be changed to “true” if it exists and is true. (If it doesn’t exist, it’ll stay as $gavePlayerFreeSupplies. If you’ve ever reported a bug where a dollar-sign token shows up in text, it’s because of a missing or misnamed memory key.)
Making It More Complex
We can also add a rule that writes a bit of text which lets the player know that Min Chen remembers giving supplies without having to ask first.
![]()
Check out bqfsAskForSuppliesGaveAlready, highlighted above. It is triggered by PopulateOptions, just like bqfsAskForSuppliesOption, so it will be queried at the same time. However its conditions require $gavePlayerFreeSupplies be true, which is only set by bqfsAskForSupplies after a player gets those free supplies.
(You might ask, don’t we need to check that $postId == supplyOfficer again? Technically, no, because this is literally the only place in the game that sets $gavePlayerFreeSupplies… so far. We could write a more restrictive set of conditions if we wanted to make this more safe. I won’t bother now. Adding more conditions to a rule also changes certain things you’ll understand if you watched the Valve video. We’ll get to that soon.)
I’ve added a new set memory to the script block, too:
$saidAlreadyGaveSupplies = true 0
The number of game-days that must pass for this memory to expire is zero! This means that the memory will disappear as soon as the player closes this dialog window, returning to the campaign map. (It will not disappear, however, if the player simply closes the comm link with Min Chen and then re-opens it.)
With that set, note that after this rule is executed and its text printed, it will not repeat until the player leaves the dialog because of this new condition:
!$saidAlreadyGaveSupplies
The exclamation mark is a logical NOT, so this says “if not $saidAlreadyGaveSupplies equals true, then allow this rule to be executed”. Simplified, it’s basically “make sure $saidAlreadyGaveSupplies is either set as false or doesn’t exist”.
![]()
We can still ask for supplies, which is a bit silly and possibly rude. Note that he doesn’t repeat the line about recognizing you. (Maybe he should – often it’s good UX for people to repeat things or state the obvious in ways that would be unnatural in ‘real life’ because it hints at important game information.)
Anyway, let’s disable the option to ask for more supplies if it’ll only result in Min Chen saying no, and add a tooltip that tells the player why the option is disabled.
![]()
Here are the three lines in the script block:
SetTooltip bqfs_askForSupplies "$personName remembers giving you free supplies. You'll have to wait until 3 days have passed since you last bummed supplies off of $himOrHer for $himOrHer to forget."
This calls SetTooltip on the option with the key bqfs_askForSupplies with the given text.
SetTooltipHighlights bqfs_askForSupplies "3 days"
Same deal, but this gives a highlight effect to the first instance of the specified text found in the tooltip already set on the targeted option. (There’s even another command to set the color of text per highlight effect! We don’t have to get that fancy right now.)
And, finally, the most important command:
SetEnabled bqfs_askForSupplies false
Which disables the targeted option, making it appear grayed-out and unselectable. Sometimes it is good to show options which are not available so that the player knows that they need to fulfill some missing condition to be able to select it.
With the new rule in play, it looks like this in-game:
![]()
This does, unfortunately, negate the possibility of encountering the rule we made previously where Min Chen doesn’t give you supplies. We have to choose which way of expressing this setback feels best in this context. (Right now, I don’t care because this is a tutorial!)
Almost Done, I Swear
Let’s return to that tangent about the conditions block using multiple condition checks.
When conditions are evaluated, if any condition fails the whole rule will not be triggered. If we’re using FireAll and all conditions pass successfully, then the rule is fired.
If FireBest rather than FireAll is called on a trigger, the number of conditions that ‘succeed’ becomes important. Each successful condition adds one point to the value of the rule, so a condition that fulfills three rules successfully will be triggered over one that fulfills just one rule.
Let’s use this so that Min Chen has some different things to say if the player has poor relations with his faction.
![]()
I cheated the system here by added a score:[number] on the end of one of the condition lines to change the value of the rule if it passes its conditions block. This can be used so an option can override others even if it evaluates fewer conditions.
To test this in-game, I’ll use some other dev-mode options to change my relations with the Hegemony so that I may encounter these responses.
(Truly, setting up the state of the game to test esoteric conditions can be a ton of work – rules.csv scripting also makes this way easier because I can make hidden rules which require $global.isDevMode
set to true, and use those to set memory values to match my test case. Later, when we do pre-release testing, it becomes important to encounter these states more naturally. This takes a lot more time, and is why it’s useful to have dedicated QA testers.)
![]()
![]()
Not bad. The whole thing should probably be overhauled so that all of the reactions work together cleanly and, as mentioned, I need to commit to expressing certain game information in a consistent manner and at consistent times within this dialog. Like, does Min Chen react with suspicion/hostility when the player opens the comm link or only when they ask for supplies? And what happens if the player returns and asks the same question?
I’ll note also that it is tempting to model rules in your head as a series of interconnected dialog nodes, only one of which is “active” at a time. They can be implemented this way, but that metaphor drastically limits the possibilities. Rules are a set of functions which affect the dialog state based on triggered conditions; multiple different rules can be used all at once (as we did with the tooltip modification and option disabling… alongside every other PopulateOptions-triggered rule!) to create a displayed state – and this state can vary wildly based on conditions.
Hmm.
Yes, this is just a tutorial, but it bothers me when things aren’t exactly right…
How About For Real
This whole asking for free supplies business is a bit silly to actually put into the game. But having walked carefully through setting up a basic version of it, I’ve been thinking about how I’d do it for real.
I’m not going to go through step-by-step, but instead just implement everything then show them to you. And now that you know how to read the raw rules spreadsheet, perhaps you can figure out roughly how this will work in-game.
Here’s the rules chunk as a .txt file: bqfs.txt You may need to change it to .csv to view it in a csv editor. (And remove the header line.)
Or, if you like, here’s the largest image on this website:
![]()
Some thoughts during/after implementing this:
- I did get a little fancier than strictly necessary to show off more features of the rules system. (And make pretty colours.)
Reviewing this with Alex turned into a bit of a design discussion about how to make this a not-annoying feature.
Basically, if the player can get supplies for free, then it is in their interest to optimize the game by taking free supplies whenever possible – and jumping through stupid hoops to do so. My first-pass implementation scaled based on how many supplies it would take to repair & CR-max your fleet, so: the player could pull a bunch of junkers out of storage to get more supplies. Or this would make it in their interest to start salvaging garbage ships from combat and bringing them back to a commission world to get free supplies. And: if we detect if the player dumped stuff in storage, or if they’re fresh from combat, they’ll just dump ’em in an abandoned station somewhere and it’ll be the same problem but create even more tedious incentives.
So, we came to two decisions: 1. Make this have a huge cooldown (one ‘cycle’) so that the game doesn’t incentivize ‘farming’ supplies. 2. This should also be limited to early/mid game. How? Well, once you have a colony, no more free supplies — you can’t very well dump an entire colony into storage, can you.
- Yes, there is code to check if the player simply dumped their supplies nearby to pretend that they’re all out.
- … In writing this just now I realized that I forgot to check if the player dumped their supplies into local storage to pretend they’re all out. Adding a note to my TODO to implement a check for this case…
- This sequence shows use of unique text for $faction responses. In addition, one can write unique $voice responses (each NPC is given a ‘voice’ memory which corresponds to a vague archetype: soldier, faithful, pather, spacer, aristo, official, business, scientist, villain. These are not used very often, mostly just in the most generic interactions so that those feel less repetitive. Multiplying text variations by 10x (1 generic, 9 voices) for everything is rather unreasonable.
To that point, I don’t make a particular effort to give every faction a unique line everywhere possible. Mostly I’ll write a custom version of text when inspiration strikes or it feels particularly appropriate to the situation. And just as with $voice, certain more generic interactions work better using faction-specific responses to add variation to otherwise repetition lines, or are necessary for flavor i.e. taking a faction commission.
- This chunk of rules does use a few custom script calls written (in Java): “recalcFreeSupplyDaysRemaining”, “isCargoPodsScam” (borrowed and modified from the code used by patrols), and “doesPlayerFleetNeedRepairs” which sets a few different variables based on just how damaged the player’s fleet is so that the Quartermaster can respond appropriately.
All of these scripts return a boolean (true/false) values as well as setting other memory values in the course of their evaluation/execution. This means that they can be used in the conditions block as a condition, and the memory variables they set can be used elsewhere (or immediately!). A common use is to call “updateData” on a mission script – this populates local memory with mission-relevant data, but will also return false if the mission doesn’t exist. So instead of two calls, one to check if the mission is active and one to populate memory, they’re baked into the same call.
- As implied by the above, there’s much to say about Starsector rules design patterns – the “conversation hub” structure is particularly useful – but I think we’ve had just about enough rules talk for today.
Whew. Thanks for sticking by through all of that.
You’ll get your free supplies in the next update – conditionally!
Comment thread here.