I think for this you could keep track of the inventory state for each player. Let's assume that you have for each player a state
I, which indicates the state of the inventory for each player. This should be written to some kind of permanent storage, otherwise this data is completely gone when the server crashes, I'll just assume a file here for now, but something like a database works as well. By default, for every player p,
I(p) is set to
none (you can save some space by not saving the
none state and treating the lack of a stored player as indication that their
I(p) is
none).
When a player
p joins your custom world, you first save their original inventory to a file. Then you set
I(p) to
original. Then you give them the custom inventory and set
I(p) to
custom. When a player joins the server and immediately joins in the custom world, you can treat it the same as them joining the custom world.
When a player
p leaves the custom world, you first save their custom inventory to a file. Then you set
I(p) to
original. Then you give them the original inventory and set
I(p) to
none. When a player leaves the server completely, you can treat it in the same way as them leaving the custom world. When the server shuts down properly, you can treat this as all people leaving the custom world.
Now, when the server shuts down properly, all players will have left the custom world and so for all players
p,
I(p) = none.
If the server did not shut down properly, there may be some players for which
I(p) ≠ none. There are two cases, either it's set to
original or to
custom.
- If it is set to original, you restore their inventory to the original inventory and set I(p) to none.
- If it is set to custom, you take the inventory they currently have (as stored by the server) and store that as the custom inventory. Then you set I(p) to original. Then you take the stored original inventory and restore this. Then you set I(p) to none.
So as a quick recap, there are three types of inventories stored for each player: the inventory that is kept track of by the server itself, which can be either the original or the custom one; and you store both the original inventory and custom inventory in some place. You also keep track for each player the state of
I.
I essentially works as an indicator of what kind of data you have saved properly, so when the server starts after a hard crash, it knows where it was.
Note that this only works if each piece of data that is being saved is also a transaction, so you should not have inventories where only some data is saved, or a state of
I that is neither
none,
original, or
custom. The saving of inventories by the server, by your plugin, and the state of
I can all be saved separately. Some journaling file systems may help with this. Alternatively, a database where the changes are done as a transaction will also do this for you.
I don't know if the saving of the server itself is atomic, or if it can be that if it crashes stuff isn't saved properly. If it's not atomic, then you still run into trouble there. However, I'm assuming here that it is, because otherwise we would need to rewrite the entire saving mechanism of the server.
If after a hard crash, the server is restarted without your plugin, or some of the player's data is missing (importantly, inventories are missing), this also won't work. But I don't think you can do much about that without messing with the server's internal saving system. If the server crashes and it starts without your code running, you also can't do anything to restore it.
If you want to know why this works, we can look at each place the server could crash and see why it would be restored properly after starting again. Let's start by joining the custom world.
- If it crashes before anything happened, the server is still in a good state. The player has their original inventory and I(p) = none.
- It crashes after saving their original inventory, but before setting I(p) to original.
We're still in a good state, with the player having their original inventory and I(p) = none. We have their original inventory stored, but we won't need it.
- It crashes after setting I(p) to original.
The player still has their original inventory, but we can't be certain of that after a hard crash. However, we have the original inventory stored, so we can restore it and we'll be good. The restoring isn't necessary, but it doesn't hurt.
- It crashes after giving the player the custom inventory, but before setting I(p) to custom.
We will need to restore the player's inventory. I(p) = original, so the next time the server starts, we restore their original inventory and we're good.
- It crashes after I(p) is set to custom.
This can happen long after the player joined the custom world, because this is after the last step. Because of this, their inventory on the server may be very different from the inventories we have stored. When we crash, we therefore first take the inventory saved by the server (which is consistent with the rest of the world) and store this as the custom inventory, so next time they join the custom world it'll be as it should be. Then we set I(p) to original, restore their original inventory and we're good.
Now for leaving the world.
- If it crashes before anything happened, it's the same as it crashing after a player joined, so this is the same as the last bullet point above.
- It crashes after saving their custom inventory, but before setting I(p) to original.
We have saved the custom inventory, but the server won't know it after starting again. So we just store again to be sure and then make our way back to restoring the original inventory.
- It crashes after setting I(p) to original.
We now know for certain the custom inventory was saved properly, but might not have restored the original yet. So we restore the original inventory and we are good.
- It crashes after restoring their original inventory, but before setting I(p) to none.
The server can't be certain that the original inventory was restored, so it restores it again just to be sure, but it wasn't strictly necessary.
- It crashes after setting I(p) to none.
The inventory is set to the original one and I(p) is also correct, so there's nothing to do.
It can also happen that after a crash, when trying to restore data, the server crashes again, so we also need to check the restoring procedures. Let's check if this is correct when
I(p) is set to
original.
- If it crashes before anything happened, the server will try again next time it starts, so that's not an issue.
- It crashes after restoring the inventory, but before setting I(p) to none.
Next time the server starts it'll restore the inventory yet again, but that's not an issue.
- It crashes after setting I(p) to none.
Everything was fully restored properly and if the server crashes now, it won't do anything for this player next time when starting.
And lastly, if
I(p) is set to
custom.
- If it crashes before anything happened, the server will try again next time.
- It crashes after saving the current inventory as the custom inventory, but before setting I(p) to original.
It'll save the custom inventory again next time, but the inventory is still the custom one, so that's okay.
- It crashes after setting I(p) to original.
The custom inventory is saved properly, we just need to restore the original inventory. It continues in the same way as described in the bullet points of the previous section, so that isn't an issue.
Assuming I didn't miss anything, that should be it. I think you're the first person I've seen that actually cares about hard crashes. It's quite tricky, but I think you'll have a pretty good basis down if you do this.