In the last post, I left out one kind of interaction with the environment - damaging objects! I decided to describe it in this post instead because attacks revolve heavily around audio and video effects.
Synchronization of audio and video effects
Let's start with the attacks. The concept of an "attack" in the game is quite complex. Each attack has a lot of parameters - preferred target (what we click on), host of the attack, damage (physical / magical / poison), weapon material, effects (poisoning, slowing, burning, pushing, stunning, etc.). If it's an arrow, then we have to factor in its appearance and flight path; same with a boomerang or fireball. All of these effects do not have a save/load code, because they are not saved when you exit the game. Instead of creating code for writing/reading all possible effects (there are lots of them), I use a simple and logical step - I send only a link to the weapon as an inventory item.
I've already implemented the sending of inventory items. The attacks of each monster are also already implemented through weapon items (i.e. each monster does not just store the damage it deals, but a link to a full-fledged item which is the same as the sword or spear that our main hero uses). The only exceptions to separate weapons include swamp water, a volcano's vent in the bowels of the mountains, and cannons that guard the pirate boss. For multiplayer, I had to rewrite their code a bit and create a "weapon" for each of them to fix it.
To synchronize the flight of an arrow, stone, needle, dart, fireball, etc., I don't transmit the position of these items for every frame. I calculate the parameters of the equation of the parabola along which these items will fly, and transmit only these parameters. Other players, having received them, can easily launch the ammunition along the same trajectory. However, the other players’ client doesn't try to determine the collision of the item with anything. It simply moves the arrow along the parabola until it receives a signal from the first player that the movement should be stopped and the item itself removed. The boomerang is synchronized in a similar way, but the trajectory there, of course, is more complex and the number of parameters is greater.
Synchronizing sounds, sparks, explosions, and other one-time effects is the easiest. You just need to tell other players at which point to play certain effects. To be able to transmit a link to the effect, I made a script that automatically scans all game files, gives each file a unique number and places these numbers into the dictionary, which allows it to quickly identify each file by its code.
Synchronization of effects and sounds was one of the most pleasant tasks - there were no difficult moments, and the result was very noticeable! We can already see how other players are running around the map, interacting with the environment, cutting down trees, shooting arrows, using magic, etc. At this stage, I posted the second demo video. You have already seen it, but I will duplicate it here:
Jumping between locations
The world in the game is divided into several locations. Moving players around these locations is not a problem. Going to another location, the player simply sends a signal to the server, the server redirects it to the other clients, after which they hide the corresponding character. The difficulty lies in the fact that locations are randomly generated when entering for the first time. If one of the clients goes to a location where no one has ever been before, that location must be created. This is what happens in a single-player game.
So what's the problem? In programming, it’s called RAM fragmentation. When the program works, it often allocates some pieces of memory, then releases, then allocates again, and so on. Then, over time, RAM becomes like a colander - small pieces of allocated and currently used memory are distributed throughout all available space. When we need to allocate memory for a new and very large chunk, the system understands that this chunk cannot be taken and the program crashes with the error "not enough memory." Although, there would be enough free memory in total for this piece.
This problem exists only in 32-bit operating systems. For 64-bit systems, the address space is so large that we will not be able to fill it with garbage. In the first part of the game, I encountered this problem because - in the early stages of development - I did not think about memory fragmentation. To generate a world, you need to allocate several fairly large chunks of RAM for the geometry of the level, its textures, grass, etc. If a player played for a long time in a ready-made game, then exited the menu and tried to start a new game, there is a high probability that the game would crash. To avoid this, I had to do a trick that - when trying to start a new game - the game asks the player to restart the application. As a result, RAM for the application was renewed and the game had a large chunk of memory without garbage.
In the second part of the game (as I mentioned above), each location is generated when you first enter it, so I needed to protect myself from the fragmentation problem. To do this, when I start the game I immediately allocate several large memory buffers for future map generation and then use these buffers, if necessary. To prevent these pieces of memory from being idle during the main game, the game uses them to store some information about the currently loaded level - height map, textures, and grass. Subsequently, it turned out that there was no sense in supporting 32-bit systems anyway and, as there is no problem for 64-bit systems, this approach turned out to be unnecessary.
In the online game, it became a significant problem. If the client goes to a new location, then the server needs to generate this location, meaning it needs to free up memory buffers for this. But it uses them for the location that was currently loaded. Therefore, I had to significantly redo the memory management.
That's it, see you in the next post!