Personal Contacts

One of the bigger tasks for this release cycle has been adding new content into the game. Since a lot of it is story content – think the “Red Planet” mission, but on a bigger scale with things tying together and building up – it’s not something that I can really talk about without spoiling it.

But, not all the new missions are “story” missions. A lot of them missions are just new things you can do in the game, without being unique one-offs. Consider, for example, the current missions to scan a derelict, survey a planet, or collect a bounty – these new missions are all roughly along these lines, with of course more variety thrown in.

The question is, how do we make them available to the player?

Current System
The current missions just “pop up” as a new message in the lower left of the screen, and they can be looked at (and accepted) by looking at their details in the intel screen. This is nice because you become aware of new opportunities as they become available.

Since only opportunities that are reasonably nearby show up this way, there’s some filtering going on, so the player isn’t overwhelmed by, say, postings for minor procurement missions on the other side of the core worlds. At least, that’s the theory. In practice, missions tend to be important enough that this filtering doesn’t accomplish too much, but with relatively few missions being offered this way, it still works out.

However, this approach doesn’t scale – add an extra 20 or so mission types, and one of two things will happen. Either the player will be overwhelmed with non-stop new mission notifications, or we tone down the rate at which new missions are generated, and the player doesn’t get enough of the ones they’re actually interested in. For example, if you’re playing as a bounty hunter, and all you’re getting is trade contracts of some kind, that’s no good.

What we need, then, is a way for the player to express what they’re interested in on any given playthrough. This could take the form of some kind of UI where you say, “I mostly want bounty missions, and some underworld ones” and you’d get notified about those.

This… would probably work, actually. One potential issue is that generating missions on the fly like that can be computationally expensive. For example – this is a bit of a simplification, but say the mission requires a specific kind of object to be placed in a specific kind of star system etc etc. Finding something matching all these parameters could potentially take too long for it to be something you can do without causing a frame drop. At the very least, it means a lot of care has to be exercised to make sure that doesn’t happen.

I think we can do something that both gets around this and is more fun, though.

Contacts
How would a proper star-faring captain – of perhaps somewhat questionable moral fibre – get a hold of opportunities? Naturally, they would do it through a network of contacts. Above-board opportunities are for suckers – I mean, you might even have to pay tariffs. The horror! And even for a more principled brand of captain, the best opportunities would still be found from like-minded people in positions of influence – they wouldn’t be offered to just anyone.

So! On a high-level, the player develops various kinds of contacts, and, as the relationships progress, they offer more interesting opportunities. What kind of opportunities the contact offers depends on who they are. The sort of contacts the player develops, then, is the way the player tells the game what they’re interested in, which the game can then deliver.

Aside from this mechanical role, this also offers the player more roleplaying opportunities, and the feeling of developing a personal connection with someone in the game world.

Contact Basics
There are three types of contacts – military, underworld, and trade. These are “tags”, so it’s possible (though very rare) for a contact to combine two of these. A contact is generally located at a colony, and when the player visits them, the contact will have some missions for them. Note that it’s “will”, not “may” – since the player needs to make the commitment to actually go visit the contact, there needs to be some kind of payoff. Of course, there’s a slight cooldown immediately after a contact is visited, but any sort of normal visiting schedule will result in missions being available.

Tying this back to an earlier point: since the missions are generated when the player talks to the contact – that is, the game is paused and in a UI interaction – we don’t have to worry quite so much about the performance of generating the missions. First of all, since the game is paused, there’s less computation being done. And, since we’re in a UI interaction and the computations happen as a result of player input, even if we did get a frame drop, it wouldn’t interrupt anything the player was doing – and happens on a static screen, to boot – so it wouldn’t be noticeable. I should say, this isn’t the biggest concern, but not having to worry about it is definitely a nice-to-have.

In addition to tags, a contact also has an “importance” – a measure of how influential they are within the hierarchy of whatever organization they belong to. There are 5 levels of importance, from “very low” to “very high”. For example, a low importance trade contact would offer missions involving smaller quantities of cheaper commodities. A high importance trade contact, on the other hand… let’s just say you might need an Atlas freighter or two. Or even a colony.

A high importance contact doesn’t just start offering juicy missions, though – getting to that point requires completing a number of lower-importance missions. One way to think about it is the relationship level caps the “importance” of the contact, so the effects of higher importance only become realized once the relationship is higher.

Developing Contacts
So, how do you find a contact? You need some sort of “in”. This takes the form of bar encounters – if you meet someone at a bar and they offer you a mission, and you complete it, there is a chance they will be a contact you can “develop”. (Most “contact” missions also have a “bar” version of the mission, by the way, so there will be a lot more going on in an average dockside bar.)

Why wouldn’t you just develop every single contact you come across? It seems like doing this would both be beneficial (more opportunities, easier access) and a grind to get all the relationships high. That wouldn’t be good, so “you and a hundred of your closest friends” shouldn’t be a thing.

Fortunately, we can leverage story points (talked about at length here) as a soft limiting mechanism. The rules are simple:

  1. Up to 5 concurrent contacts are “free”; deleting a contact is also free
  2. Contacts beyond 5 require a story point to develop

This way, the player can ultimately develop as many contacts as they like, but since story points have other uses, it’s no longer “optimal” to just develop every potential contact. (In an interesting parallel, story points are *also* a mechanism the player can use to express what they’re more interested in doing. E.G. investing them into having more contacts would be one way of doing so.)

Priority Contacts
Going further in the direction of letting the player express what they’re interested in, each contact can be designated as “priority” or not. Priority contacts offer more – and higher quality, as if the relationship was higher – missions.

The more contacts are flagged as “priority”, the less the effect on each individual contact. Also, turning this flag on takes about a month to take full effect – so that, for example, you don’t just have to toggle it on right before talking to any specific contact.

Mission Examples
Here, I just want to mention some examples of the various missions that you might find being offered by a contact – or at a bar, by a potential contact.

  • Perform a “combat extraction” – use the new raid mechanics to rescue an agent, either stuck at a colony, or perhaps at a pirate base on the fringes of the Sector
  • Deliver an item to a “dead drop” location
  • Disrupt spaceport operations at a target colony for at least X days
  • Begin producing at least X economy-units of a commodity at a colony you control to fill a cycles-long contract that will pay you for this production
  • Smuggle a load of cargo to a colony
  • Collect a special bounty

The missions also generally have “complications”, so even ones that don’t on the face of it lead to the possibility of combat, will often do so. There’s also an emphasis on using the new raid mechanics for various custom objectives. This is both adding variety to missions, and giving marines (and ground combat related skills) a more prominent place in the game.

Special Bounties
The “special bounties” mission is a good one to elaborate on, because it triples-down on letting the player express what they’re interested in (it’s apparently today’s theme!)

The basic mission is straightforward – you receive a bounty for eliminating a target. There is a pool of a bit over ten different types of bounty targets, ranging from the standard (pirates, deserters) to more exotic stuff like REDACTED stations and fleets, elite mercenaries, and faction patrols (when given by underworld contacts), and some other ones I won’t mention, because spoilers. The more unusual target types become available as the difficulty (and reward!) of the bounties ramps up.

This is where it gets interesting – managing what difficulty level is appropriate. Internally, the difficulty is a value from 0 to 10, and it goes up gradually as the player completes more bounties for that contact. However, when being offered a bounty, the player is also offered a choice – do they want an easier, average, or tougher target? The “average” choice results in the difficulty going up slowly over time, while the easier/tougher choices quickly take it up or down.

For example, if the player already has a strong fleet, but is developing a new contact, they can start off with a reasonable target right away, and get to the highest bounty level by selecting just a few of the “tougher” bounty options in a row. (Please pardon the placeholder text in the screenshot below!)

On the other hand, if a player is facing bounties that are too difficult – either as a result of losing some ships, or due to the normal difficulty growth from completing bounties outpacing the player’s progression – they can easily turn it down a notch. Since these new high-end bounties are some of the most challenging enemies in the game, that’s a useful option to have.

(The choice of bounty difficulty, by the way, is not just scaling the strength of one specific target – I think it would feel pretty odd to do that. Rather, there are three separate bounties that the player can choose from.)

The system is also set up to make it easy for mods to plug in additional types of bounty targets.

Modding
Speaking of mods, I’d like to talk a bit about how the new missions are implemented. It’s a lot of new content, so it made sense to first spend some time putting together a system that makes creating this content much easier. (This part will get a bit technical, so if it’s not your cup of tea, feel free to skip it.)

There are a bunch of common tasks one has to do when creating a new mission. Some quick examples are, say, “find a star system / planet / entity matching some set of parameters”, or “create a specific type of fleet and give it certain orders”. These are not very complicated! But, annoyingly, the considerations are usually complex enough that just writing a single method with some arguments isn’t a good option. And there are also mission state transitions and such to consider.

The bigger problem, though, is that the coding style for these is what’s generally called “imperative” – the code for a mission would spell out exactly how to find the right star system, or spawn the fleet. This means that the programmer (i.e. myself, or a modder) has to be quite careful and detail oriented, and do a mental “context switch” to immerse themselves in the technical details of what they’re doing.

This means that coming back to the mission code after doing something else is a pain – it takes time to re-immerse in what the code was doing. Even if you’re just switching back and forth between coding the specifics of the mission, and writing some dialogue for it, there’s a very real mental cost here. It means both more time spent per mission, and more bugs (which can be found and fixed, but still).

New Approach
It’d be nice if instead of code, the specifics of the mission could all be data entry. Then you wouldn’t have to think about the details so much when putting that together. We can’t do that, can we? … well, probably not as such, but after all, all code is data (and vice versa, really). There’s a middle ground here – creating a basic framework that focuses on making mission code “declarative” – that is, describing what needs to be done, rather than how it needs to be done.

What this means is that writing mission code won’t require as much thinking about the details – it’ll be easier to just do something, and be confident that if it works in major ways, it just works, and there aren’t minor errors lurking somewhere. This could really do with an example. Let’s say you’re trying to find a star system that… I don’t know, has at least 4 planets in it.

“Imperative”-style code would look like this:

List<StarSystemAPI> potential = new ArrayList<StarSystemAPI>();
for (StarSystemAPI system : Global.getSector().getStarSystems()) {
    if (system.getPlanets().size() >= 4) {
        potential.add(system);
    }
}

And then we’d pick one of those potential systems. And, by the way, that’s a trivial set of parameters – and yet, there’s a bug in that code. The “getPlanets()” method returns stars as well as regular planets, as under the hood they’re just different types of planets – so we’d need to subtract the number of stars in the system from the total. Now, I’d know this particular bit offhand, but there are tons of cases where I wouldn’t be 100% sure, and writing correct code would require looking more under the hood. That’s what I mean when I’m talking about needing to become “immersed” – it’s remembering all the little details in the area of the code you’re working with.

Declarative Example
With the new system, we can instead write more “declarative” code to solve the same problem:

requireSystemHasNumPlanets(4);
StarSystemAPI system = pickSystem();

… and that’s it, actually. That’s all there is to it! The code that runs as a result of this knows about the “don’t count stars” issue, so we don’t need to worry about it every time we’re looking through some planets. Again, it’s not hard, but if I’ve got to do it 20 times in slightly different ways (that are, as I mentioned, usually annoyingly hard to parameterize), chances are I’ll probably screw it up at least once.

We could also easily add some more search parameters:

requireSystemNotHasPulsar();
preferSystemUnexplored();

This’ll search for a system that’s not going to fry the player to a crisp with a pulsar beam when they enter it, and preferably one that they haven’t been to before. Being able to do this requires creating a ton of these kinds of methods – that implement various search parameters and other actions. But that’s a one-time task, not something one has to engage in every time when adding a new mission.

More Examples
(I won’t show equivalent imperative-style code here – in large part because it would be too long and complicated for this post. Which, really, just demonstrates the point.)

Wiring up basic mission state transitions and similar:

setStartingStage(Stage.GO_TO_RUINS);
addSuccessStages(Stage.COMPLETED);
addFailureStages(Stage.FAILED);

makeImportant(planet, “$gada_targetPlanet”, Stage.GO_TO_RUINS);

connectWithGlobalFlag(Stage.GO_TO_RUINS, Stage.GET_IN_COMMS_RANGE, “$gada_gotData”);
connectWithInRangeOfCommRelay(Stage.GET_IN_COMMS_RANGE, Stage.COMPLETED);

setTimeLimit(Stage.FAILED, MISSION_DAYS);

Spawning a pirate fleet at a jump-point and making it go to a planet and patrol around it, triggered when the player is approaching the system in hyperspace:

beginWithinHyperspaceRangeTrigger(planet, 1f, false, Stage.GO_TO_RUINS);
triggerCreateFleet(FleetSize.MEDIUM, FleetQuality.DEFAULT, Factions.PIRATES);
triggerSetStandardAggroPirateFlags();
triggerFleetAllowLongPursuit();
triggerPickLocationAtInSystemJumpPoint(planet.getStarSystem());
triggerSpawnFleetAtPickedLocation(“$gada_pirate”, null);
triggerOrderFleetPatrol(planet);
endTrigger();

There’s a large set of methods that make common tasks easy. Probably the pinnacle of this is being able to, with a single method call, under certain conditions spawn a fleet that will talk to the player, make certain demands (or perhaps a lucrative offer), and fail the mission if they accede. Being able to do these kinds of things easily, on a whim, is such a difference from having to write even fairly simple code that has to do this imperatively.

It’s also possible to add new methods in the same style, and – since this is in code, not a text file – it’s also easy to resort to imperative-style programming for one-off mission specific tasks that it doesn’t make sense to try to make into general-purpose units.

Missions, Bar Events, and Contact Tags
If you’re a modder, this is probably on your mind – how easy is it to add new missions for contacts? The answer is “very”. There are a couple of new spreadsheets – for bar events, and contact missions – and adding a new mission there is all it takes to have it show up. It’s also possible to define entirely new tags for contacts, to expand the current set of “trade/military/underworld”.

 

Comment thread here.

Tags: , , , , , , , ,

This entry was posted on Thursday, August 13th, 2020 at 5:16 pm and is filed under Development, Modding. You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.