NetworkCompatibleCode
HowTo write a code that works in a network game
Network Compatible Code
During multiplayer, the gamestate must be the same on both machines at all times. TTD doesn't transmit the whole gamestate date to achieve this, though.
Instead, every user-initiated action is transmitted to the other computer, and both machines execute it at the same time. It is important to note that the random generator seed is part of the gamestate, too. When everything goes well, the gamestate (including the random seed) is in sync because both machines do the same things and generate the same random numbers. When things go wrong, however, the random seeds will start to differ soon (the first time one side calls randomfn without the other doing the same). From this point on, random events will start to differ on the to machines, making the gamestates drift further and further apart from each other. The easiest way to detect this problem is comparing the random seeds of the two machines; TTDPatch does this to detect desyncs.
An action is transmitted only if it's called through ))DoAction[[]]DoAction[[, you should use it for every action that changes the gamestate. (The dopatchaction macro calls ))DoAction[[]]DoAction[[ behind the scenes, so it's safe.) The only information transmitted is the input registers of ))DoAction[[]]DoAction[[, and the content of textrefstack. Therefore, you can't use global variables to communicate with the action handler.
You should indicate errors via setting ebx to 0x80000000, and setting operrormsg2, if appliable. This way, both PCs will see that the action failed.
The action handler must do the same things on both machines. This includes checking whether the action is possible at all, even if you're sure that it is possible when calling ))DoAction[[]]DoAction[[. The games may be out of sync already, so the action may not be possible on the other machine. Additionally, with the new asynchronous protocol, actions won't be executed instantly, so the action may become impossible by the time it gets scheduled.
Calling the random generator at only one PC will desync the games.
You can't pass pointers into ))DoAction[[]]DoAction[[, you have to convert them into an index because the memory locations on the clients may not be the same.
This means:
- Always use actions to change the gamestate, so both PCs will do it.
- Don't use global variables for communication between normal code and action code. Bad example: Set flag -> call ))DoAction[[]]DoAction[[ -> action code tests for the flag - the flag is now only set on one PC, and you get a desync.
- Always pass back an error if something fails. Or in other words, if the action fails, return an error and if something costs money, it has to cost the same on all PCs.
- Don't change the landscape/gamestate when the action flag says to do a test only.
(There may be exceptions, or it may be irrelevant because the action doesn't have the concept of testing-executing a command. This is true for gameplay setting changes, however this is surely not true for landscape tools, building stuff, buying vehicles as they may fail because of money)
- Don't use the random number generator outside action handlers.
- Don't assume a company is an AI player, use the relevant code pieces to check if it's an human player.
- Don't pass pointers to code or variables. Example: Don't pass a pointer to a vehicle entry, but the relevant index into the vehicle array.
- Don't write code that assumes a window is opened, or on a kind of state, or try to access a window. The window is opened on one PC only.
- Don't try to open a window in an ))DoAction[[]]DoAction[[, unless it should be opend on both ends. Be sure that the window is network aware if it pops up on both PCs.