Breathing Life Into The Merchant Class
For those who are following the Pathing Saga, I managed to suss out a solution to my “dead-end” woes by reversing the pathfinding attempt should the first pass yield a dead-end from origin to destination. The reason behind the dead-end situation is that while all gates now connect to at least two sectors, some gates are closer to the destination than those that participate in a more valid route, and because we shut down routes that were determined to be “inefficient”, we can’t backtrack or even move away from the destination in order to get back on track. Reversing the process — calculating from destination to origin — helps solve that in tests because the dead-end sector doesn’t even play a part. Should there be a new dead-end scenario, well…we’ll lie and say there’s no pathing possible.
Now that the route calculation is in a form that I can build upon, I have to re-imagine my NPCs as actual interstellar citizens. If you recall (or don’t want to read back-posts to find out), the current NPC implementation is pretty minimal. A merchant will be instantiated at a random jump gate, ID and fly to a random station in the sector, spend some time there to pretend it’s engaging in Commerce, and will then fly towards a random exit gate and “jump” out of sight. For the player, this looks all above-board, like NPCs are doing similar things to what they’re doing, when in reality the NPC is just aimlessly driving around in tight pattern, appearing and disappearing to make things look like they’re Doing Commerce.
One of the aspects of the game that needs attention is making the commodity market move on it’s own. If the player can ID a static route between two stations which allows him to buy at one and sell at another without having to move around to find better deals, then it’s a failure of purpose. We can avoid this by simulating the moving of goods through “other means”, and one way of doing that is by employing the NPC merchants to actually buy and sell goods to get them out from under the player and alter the stability of trade routes to some degree.
Here’s the framework of a new merchant NPC plan that I roughed out on paper:
- Right now, merchants are instantiated from prefabs in a sector where the player is. That’ll be altered according to the following plans.
- I need a new data object which represents everything about a merchant. This data object will be used to record an NPC “personality” in the Master Data Store so I can create “named NPCs” that the player will (hopefully) run into here and there to provide some kind of familiarity over the course of play. Yes, I will name them after people I know, with their permission.
- Some of the more important properties of this data object that will help in the simulation would be the current route, the current sector, the origin and destination stations, a simulation timer, wallet, and current cargo inventory.
- The NPCs would be loaded from the MDS (new game) or from the state file (save game) and put into the merchant pool. This will be what we’ll reference when we need to simulate NPC activity.
- Every simulation tick, we’ll check on NPCs to see what they’re doing right now, and what they need to do next:
- If they’re docked at a station, they need to calculate a destination sector and station based on the goods they have purchased (probably the lowest cost SELL that they can afford and/or fit in their holds). Then they’ll undock and move to the next sector and wait for the next tick.
- If they are “in space”, they’ll move to the next sector in their route.
- If they are “in space” and their next sector is their destination sector, they’ll dock at the destination station, sell their goods, buy new goods, and wait for the next tick in order to undock.
- If it’s determined during the sim update that their current sector is the same sector that holds the player (via global CurrentSector property used to track the player), then we need to slow things down and instantiate a merchant for visibility
- The NPC data object will have a reference to a model which will be stored in state so we have uniformity throughout the rest of the game.
- The model will be instantiated and the relevant data copied from the data object to the appropriate component properties (mostly ship info, but also data object ID for refer-back to the global data store)
- Where the model is instantiated will depend on what the NPC is planning to do next:
- If they need to leave a station, they’ll appear outside their current station. They’ll then move to the jumpgate which leads to the next sector in their route. Jumpgate mechanics will handle the Destroy() and will put the NPC back into “sim mode”.
- If they are passing through a sector to the next sector, they will appear at whatever jumpgate leads to their previous sector. They’ll then move to the jumpgate that leads to their next sector. Again, gate mechanics will handle putting them back into background sim mode.
- If they need to dock at a station in that sector, they will appear at a gate leading to their previous sector, and will move to the station. Once inside the station, they’ll sell and buy as normal via the sim, and will wait for the next tick. If the player is still there, they’ll appear and move. If the player has left, they’ll undock in background sim mode and restart their rounds.
- In the cases where the NPC is buying and selling, none of the activities happen on the prefab; they’ll still happen in the simulation layer (wallet amount, cargo inventory, etc). The prefab instance will have no info on aside from a pointer back to the global data object, and any info we need to use to present a visual to the player (model ID, name, portrait, or whatever).
The sim of all NPCs will be handled by the NPCController which is currently in charge of setting up NPC pools and spawning the instances in a sector. It currently only runs these processes in the active sector, and doesn’t do anything for any other sectors in the known database. I’ll be adding in the simulation process so that the simulation runs through the data and does what it determines it needs to, checks for the need to instantiate, and handles the instantiation using the current logic in the NPCController for merchant NPCs.
So I guess I need to answer the question “why bother”? This seems like a lot of work for something that the player won’t see, and which could probably be modeled easier by randomly instantiating NPCs here and there, and just redistributing commodities via a background process.
The main goal is to allow players to ID NPCs by name. I want there to be some kind of investment in the universe, and when things become familiar, we tend to feel some kind of kinship or ownership of those familiar elements. Even if there’s no direct interaction with the NPCs, seeing the same named ship may provide impetus for the player to, say, defend the NPC from a pirate attack, whereas with a generic new NPC the player might just pass by…or attack the NPC themselves.
Another reason is because I want there to be a level of logic and realism involving what are the player’s “main competition”. If NPCs could teleport between stations, buying and selling, then they could quickly outstrip the player and potentially unbalance the economics by clearing the shelves or meeting commodity quotas before the player has a chance to reach those stations. Since the player is governed by time and relative distance, this puts the player at a severe disadvantage. Simulating the time it takes for a ship to move across several sectors, dock, buy and sell, and undock gives the player some padding to “get ahead” of NPCs. It also allows the player to follow an NPC. With the instantiation plan, a player should be able to tail a merchant to a gate, jump through, and see that NPC on the other side (with some creative license re: timings). They should be able to follow the NPC to their destination, and then see the NPC leave and start a new route. Would a player do this? Maybe they would, just to see if they could, but they could also decide that they want to scan an NPC that crosses their path, and might decide to engage in piracy of their own. In that case, having cargo matters, and knowing when we should and shouldn’t instantiate a specific NPC in the player’s sector also matters. Going a bit further, if the player could open comms with an NPC, maybe the NPC could offer the player an escort job (if the cargo is worth enough) as an ad hoc mission. None of these could happen in a pleasing manner using the current per-sector instantiation method I have in place right now.