Improved Mission Spawning
This proposal is the distilled form of a discussion on IRC about spawning missions not based on the player's actions, but based on time.
Note that this article speaks only of missions, but everything written here applies to events as well.
- Avoid the takeoff/land exploit that generates new missions
- Allow new missions to spawn even if the player never jumps or lands
Current and desired behavior
At the time of this writing, missions are generated at two points during gameplay: when the player lands, and when the player enters a system. Each time the player performs one of these actions, missions are spawned based on a random roll. This implies that the bar and computer will have a new mission population each time a planet is visited, and missions that occur in space always start as soon as the player enters.
The above behavior should be replaced by continuous, semi-persistent mission spawning that, for all means and purposes, is independent of the player's actions. Missions on planets should pop up and disappear as time passes, and a new space-based mission might pop up at any time during the player's travels.
Hashes and global tick
Introduce a new mechanic that generates a completely random hash at regular time intervals, measured in game time. We shall call this the global hash. At any given time, there is one current global hash, which is saved in the player's savegame and will be replaced by a new hash at the next tick.
Each mission-related object in the game (systems, planets) also has a hash associated with it. This hash is static, generated using PRNG (pseudo random number generator) based on the object name (for instance). We shall call this the local hash and it shall be generated during game initialization (loading).
Finally, each mission instance also receives a PRNGed hash, based on the mission name, the current global hash, and the mission instance number (in case of computer missions).
Each mission instance hash can be seen as the combination of the 3 hashes and instance value:
instance_hash = global_hash XOR generator_hash(planet,system) XOR mission_hash XOR instance_number_hash
This instance hash will be used to initialize a PRNG used during the mission's create() call.
When a new interval starts, the game determines which missions will spawn during that interval. This is achieved by running the usual mission spawning logic, but all calls to rnd() in the spawn logic as well as the create() function of missions are not fully random; instead PRNG will be used with a (deterministic) function of the global and local hashes as its seed. This ensures that even missions with random parameters (such as cargo haul missions) will consistently spawn with the same parameters until the next global tick.
If a mission passes its spawn logic, a spawn interval for this mission is generated. This interval begins at a random moment within the current global interval, and ends at the same moment during the next global interval. If the current time is within that mission-specific interval, then the mission will spawn if all other conditions are met. This creates the illusion that missions are being spawned continuously. This also means that the global hashes of two intervals will have to be stored in order for the overlap between both intervals to be handled.
As soon as a mission instance is accepted by means of misn.accept(), that mission instance may not spawn again for the duration of its interval. To achieve this, the game keeps a blacklist that contains mission instance hashes. Whenever a mission tries to spawn whose hash appears on the blacklist, it is immediately terminated. The blacklist is saved in the player's saved game, and is constantly cleaned of outdated hashes.
Putting it all together
The process of generating a mission will be the following:
- Generate instance_hash
- Make sure the instance_hash is not blacklisted
- Initialize PRNG with instance_hash
- Make sure the time is correct for the instance (interval is generated from PRNG)
- Run conditional mission checks using PRNG instead of regular RNG
- Run create() function of the mission if applicable
- If accepted, add to blacklist
In order for the saves to be sane, two global hashes will have to be stored and the date of one of them in the saves. This allows the saves to remain consistent. Additionally, the blacklisted hashes will have to be stored. It is also important to clear the blacklist when hashes are invalid in case of collisions and to avoid long term bloat.