In this rant style blog post I start by talking about the faction update and become progressively more abstract as I go. The piece ends with a soapbox rant about why hiding information from yourself is the best practice for staying sane while working on complex code. In hindsight, this rant might exist precisely because I have failed to stay sane, so take it with a grain of salt.
I haven’t built a lot of faction systems for video games, however I suspect the one I’m building may be interesting to some of you. If you’re interested in software design and logic problems, please enjoy the following rant. I’ve got to get this out of my head one way or another so you may as well enjoy it.
I imagine the simplified concept of a faction in an open space game like this as an intelligent agent that plays a strategy game over the game world. It controls a number of smaller agents, often with their own AI and behaviors, to accomplish its goals on a galactic scale. In other words you have the map, and a bunch of ships on the map, and you want to give your faction a personality by having it spawn ships and move them around in meaningful ways. A faction might make decisions to (for instance) send convoys, patrol zones, or protect points of interest with various ships that it controls.
But here’s where things start to break down In Zero Falls: the behavior of a ship can’t always be controlled because a ship isn’t an entity, it’s a location.
Well…actually it’s even worse than that because a ship IS an entity, until the player gets close to it and looks at it, and then it becomes a location, and then after a while maybe the player goes away and it becomes an entity again, but a different type of entity. Sometimes the behavior of the ship as a whole depends on the activities of the crew, and the crew might decide to fight each other instead of following orders. other times the behavior of the ship doesn’t exist, and you need to move it around the way you would control a chess piece. Lets not even talk about how the world revolves around a source of unstoppable chaos (the player) who might start flying your ships while your crew are still in them, or shoot your ship into halves which both have some of your original crew in them…what a mess.
Now’s the part where I question the reason I decided to make the Wayward Terran Frontier engine in the first place. I suppose it was at least partially because I figured it was an opportunity to make something actually unique in the gaming world. This game where you have ships full of living crew members isn’t a unique idea…we’ve been wanting this game since 1966 when we first met captain Kirk and the Enterprise. The issue (as it turns out) is that some aspects of this game are particularly hard to program and as a result people wiser than myself usually decide not to program it at all. The end result is that there are actually very few games that let you do real time combat between ships that have crew running around inside them, however they are starting to pop up here and there. I’m personally pretty excited about star citizen which recently showed off what can be done with a few tens of millions of dollars behind your dev team. Of course I’m here to talk about what can be done by 2 people and almost no money and I digress.
Sometimes a ship is an icon representing a ship. It will spawn at some point in the future turning it into a real ship, but until then it still needs to act like a ship and do the things a ship would do. Sometimes that ship is a big fancy location full of crew with their own AI and behaviors and events to deal with. Other times, that ship is a cached data blob representing whatever state the ship interior was in when the player last saw it. So how do we get that those things to respond to orders from a faction controller without going insane a little?
Lets go a little insane and list some of the issues.
For one, a crew member is pretty much autonomous. Their top priority is their own self preservation, so if the walls around them are on fire, or if all the air gets sucked out of the room they’re in they stop caring what your orders are. Thus active crew need a passive sort of control system: you give them an objective and wait for them to figure out how to accomplish it. If you want them at a location, you order them there, and then periodically check to see if they get there. On their own they will put out the fires, and take control of the ship, and then figure out what to do with it eventually (maybe) accomplishing the task they were given.
That passive control system is directly at odds with an iconic representation of ships that don’t do anything on their own. Outside of the sector, ships are simplified for lots of reasons and they can no longer think for themselves. They need to be moved around by some agent that is able to simulate their activities for the sake of universe continuity.
The issue then arises at the boundary between the two modes where the ship stops being controlled by a bunch of crew members and starts being controlled by the logic governing ships. That’s the big ugly beast that faction code was supposed to tame, and it really didn’t before.
My first design for faction code tried to have the faction itself handle giving orders in different ways depending on its own internal proprietary representation of how the ships existed. It was a mess. Faction code was huge, messy, and full of code replication because every single faction had to figure out if the ship it was ordering was a ship or an icon. The factions had code for updating their icons, they had code for saving and loading those icons, and they had code for updating and tracking ships, and they had code for converting between the two, and they ran into all sorts of issues where they would fight over control of stuff with other factions.
Worse still was the factions didn’t have a strong consistent system for remember who owned the crew members inside a contested ship, and factions didn’t really know each other existed. As a result my own code was fighting itself. Sometimes a faction would declare that every crew member inside a ship belonged to it because it had saved a reference to that ship somewhere that said it owned that ship, that caused a hilarious bug where the player’s character would stop responding to keyboard input and instead wander around performing tasks assigned by the pirate faction overlord.
Factions, in other words, were a mess and needed to be scrapped. I threw out all of the code and started from scratch with a better solution.
Essentially I decided that I had given factions too much information, and too much control. They needed to have information hidden from them. What factions needed was a layer of obfuscation that made the concept of “ship” and “icon” invisible to them so they could just go about their lives ordering things around while the game sorted things out for them behind the scenes.
We have a lot of layers in the game at this point…sometimes I look at the code and I’m shocked by how huge it is. I often wonder how I can iterate on it at at all any more, but the way I do is by abstracting things from myself just like I did with factions in this patch. When I work on a component for my code, I usually try to make it into a black box. I create mysterious entities who’s internal workings are invisible and hidden from me, but who’s external functionality is obvious. I do this because as soon as I’m done making them I have a few beers and completely forget everything I’ve just done.
Factions now work that way. There is a layer of abstraction that tracks all ships as they move from the faction’s “spawn” method, to the game world, to cached data blobs, to the hard drive, and back again. Do I remember how it works? Maybe I won’t tomorrow, but what I know right now is that ordering a ship to fly to a certain sector in space is a single line of code for a faction to perform, and it’s going to let me do all the cool stuff with factions that I wanted to do in the past. Additionally, all of the issues surrounding the transition of a ship from inside the sector to outside the sector are now handled in a single location, so if there are bugs in the future I only have to read through code in a single location to potentially fix the issue for all factions, where before I would have to read through all code of every faction to find an issue.
Factions can finally do what they are meant to do and worry about what they were meant to worry about, and now I am free from all of the burden that was preventing me from building complex behaviors into factions. Patrolling flotillas with escort ships, convoys making important deliveries, ships that actually perform tasks instead of mindlessly flying from point to point are all on the drawing board and will be going in very soon.
Rewriting factions from the ground up was actually easier than fixing the existing factions, and the new system is much better because most of the information and control has been taken away. Now when I look at the code for a faction I see only the fields and methods that a faction should necessarily care about: spawning ships, moving ships, putting loot into ships, etc. It makes it easier for me to program and debug, and thus it will make factions better in the long run.
I guess the moral of the story is that programming projects can easily grow larger than the space available in a programmer’s brain. In order to keep iterating on a project after a certain point, you need to make it smaller by breaking it into pieces that do fit into your brain. I think that’s part of the reason object oriented programming is so popular. Objects and classes are a form of abstraction. They turn complex ideas into symbols that are easier to deal with by hiding information about themselves. They exist for the sake of the person trying to use them. Objects don’t exist to make programmers write better code, objects exist to make programmers manage their understanding of code better so they can write at all.
So if you ever wondered why you might make a method or a field “private” instead of public, consider this: Future you is a completely different person who doesn’t have any clue what you are writing right now. You are building a tool for them to accomplish their job, and the simpler that tool is the easier their job will be.