Conquest uses our fully custom-built platform-independent Proxy Engine, which is primarily based on OpenGL, OpenAL, and boost, along with a couple minor utility libraries like SDL and curl. Our main design goal for this engine is to keep things as simple as possible with a "minimal and complete" interface. A lot of engines and programs out there introduce unnecessary difficulty due to fancy naming and abundant interfaces, trying to predict every possible usage scenario and complementing it with a corresponding interface - while we try to strip things to their bare essentials. We also achieve this with 0 cost, since all our tools are free or open-source.
Currently there are eight sub-modules represented by their different shared libraries:
- ProxyCore contains a variety of utility classes (e.g. mathematical objects, random number generation, a thread-safe logger, exception handler, and string manager) which are essential for every project even on the lowest tier. It also offers a very powerful singleton template (minimal integration effort, reverse destruction order, works across shared libraries).
- ProxySystem provides interfaces for application, time, file, and process management.
- ProxyNetwork enables fully asynchronous network communication and file transfer.
- ProxyVideo mainly contains classes representing video resources like Display, FBO, Shader, Mesh, Texture, Font, and Animation, along with their respective managers (which are singletons).
- ProxyAudio has Sound and streamed Music (plus managers) for audio playback, as well as a Mixer class for audio source management (sort by listener distance etc.).
- ProxyInput offers comfortable input querying with a Keyboard and a Mouse singleton.
- ProxyScene forms a flexible scene graph, where clients simply attach components like Camera, Light, ParticleEmitter, MeshEntity, ModelEntity, AnimatedModelEntity to container views. It also offers post effects like Bloom and Desaturation, along with a PostEffectManager allowing post effects to share textures for increased efficiency.
- ProxyGUI, like ProxyScene, is a tree structure, but just for GUI components (e.g. Image, Label, Button, Panel) instead of scene components. Individual messages sent out by components (like button clicks, panel moves, etc.) are propagated via observer pattern, which facilitates a very comfortable implementation of complex GUI interactions (components simply add themselves as receivers to other components they're interested in).
To compensate a lack of manpower concerning content creation, Conquest uses a powerful system for generation of attractive random worlds. Different raw heightfield tiles (insular shape) are randomly combined, scaled based on terrain type (plains, mountains), then smoothed with a gaussian kernel. Based on ground characteristics like height and slope, texture maps are rendered out via texture splatting, storing the ground texture distribution in the color channels (which then get blent together in realtime). All geometry is stored in vertex buffer objects for increased performance, culling is performed on a per-territory basis.
Game Server as Separate Process
We create a new process for the server when a user creates a game, be it single- or multi-player. This allows us to fully split the client and server logic and makes for cleaner code and more defined interfaces. The local client communicates with the server through TCP, the same way as every other client, but we also have shared memory allocated for signaling completed initialization to the client.
Game Server Spawning
For scalability and to prevent unnecessary resource usage we dynamically spawn official game server processes.
For example, currently we are running 6 different official server world configurations. With no one on any of the official servers, that means 6 processes running. If a player joins one, another one would go up with the same configuration (if the maximum amount of servers with that configuration is not reached and none are empty). If the player then leaves the server, the new spawned one would go down in 5 minutes (max).
This is accomplished using shared memory and process control. Using boost::interprocess we keep an array of process IDs and "server empty" flags in shared memory for each configuration. When a process first starts it finds the first unused slot of its configuration and sets it to its own process ID. When a player joins a server, the server checks if there are no empty servers of the same configuration running and the limit is not reached, and potentially starts a new process using our process control class. Every fixed interval each empty server checks if there are any other empty server, if so, it removes itself from the shared memory slot and dies. The shared memory array is also regularly cross-checked with the process list to free slots of processes which did not remove themselves.
Automated Build System
We have a build system in place, which can build and update Conquest for Windows, Mac OS X and Linux with a one-liner:
Resources/Unix/MakeUpdate.sh && Resources/Unix/UpdateLive.sh && Resources/Unix/PublishUpdate.sh
This requires no manual user interaction, but it does need a specific Mac connected to the network for the OS X build, since (as of our knowledge) there is no solid OS X cross-compiler for Linux. Some details follow.
- MakeUpdate.sh updates the working copy with the latest SVN revision and compiles the source code for all the different builds. In the case of Windows and Linux it uses the local compiler and cross-compilers, and for OS X it SSH-s to a Mac on the network, SVN updates, builds it and copies the resulting files. CMake is really handy here.
- UpdateLive.sh copies the built files into the live or with "-d" development location. The neat thing here is that we can restart the master server so it uses the last version, but leave the official game servers up to let players finish their matches, since the master server can just alert the servers that they are outdated. The players on these servers receive a notification, and when the match ends the game server restarts with the updated version.
- PublishUpdate.sh generates an update (moves the built files around to create the proper file structure and compresses binary files) and a file list (a list of game files and their corresponding checksums) for each platform and publishes both the game files and file lists with rsync. "-d" for the development version works here as well.