• entries
10
2
• views
1537

3D Fantasy Online RPG located in ancient Greece.

Items and AI

Items I was thinking about weapons and equipment's, which all are Items. So sooner or later some item system must be implemented. All items are stored in a database table (let's name it game_items) with a name, value, the type of the item (weapon, armor, crap...), a 3D model file (which is loaded by the client) and an optional Lua script file, because e.g. weapons are scriptable. In OOP terms the game_items table has a list of item classes. Now we just need another table with the objects, because when an item drops for a player and the player picks it up, the player doesn't want that this item disappears, so it must be stored in the database. Let's name this table concrete_items. This table contains what item it is (so the primary key of the item in the game_items table), where it is stored, to which player it belongs, item stats (like damage for weapons), and some other information like creation time, value etc. Drops When NPCs die, they may or may not drop a random item. Each item has a certain chance to drop on a certain map. This requires another database table which associates an item with a map and a chance to drop on this map. Selecting a random item from a pool of items that may drop on the current map isn't really hard, there are algorithms that do that, like WalkerMethod which is O(1), or my adaption of it. Items drop for certain players, other players can not pickup an item dropped for a different player. When an NPC dies and it drops an item, an item is picked from the item pool and a new concrete item is created and inserted into the database with random stats. A random player is selected from the party that killed the NPC and this new item drops for this player. If a player picks up an item, it is added the the players inventory. Technically just the storage place of the concrete item is changed from Scene to Inventory. AI First I thought I'll make a PvP only game because then I don't need to mess around with game AI, but I realized even PvP games need some NPCs that have some kind of AI. Then I came across a behavior tree library (SimpleAI) that seemed to be a nice fit. It was really easy to integrate this library into the game server. ai::Zone A Game instance has a Map object with the terrain, navigation mesh, the octree and now an ai::Zone object. ai::ICharacter and ai::AI The class hierarchy of game objects looks a bit like this. SimpleAI controls the NPC via the ai::ICharacter class. The NPC class could just inherit also from the ai::ICharacter class, but I try to avoid multiple inheritance where I can, so the NPC class has a ai::ICharacter member. Then the NPC has an ai::AI object which has the behavior and does all the "intelligent" stuff. Behaviors Behaviors are defined with simple Lua scripts, e.g.: -- Try to stay alive function stayAlive(parentnode) -- Executes all the connected children in the order they were added (no matter what -- the TreeNodeStatus of the previous child was). local parallel = parentnode:addNode("Parallel", "stayalive") parallel:setCondition("IsSelfHealthLow") parallel:addNode("HealSelf", "healself") -- TODO: Flee end -- Heal an ally function healAlly(parentnode) local parallel = parentnode:addNode("Parallel", "healally") parallel:setCondition("And(IsAllyHealthLow,Filter(SelectLowHealth))") parallel:addNode("HealOther", "healother") end -- Do nothing function idle(parentnode) -- This node tries to execute all the attached children until one succeeds. This composite only -- fails if all children failed, too. local prio = parentnode:addNode("PrioritySelector", "idle") prio:addNode("Idle{1000}", "idle1000") end function initPriest() local name = "PRIEST" local rootNode = AI.createTree(name):createRoot("PrioritySelector", name) stayAlive(rootNode) healAlly(rootNode) -- ... idle(rootNode) end So now we have a behavior with the name "PRIEST" and all NPCs with this behavior try to stay alive, heal an ally or do nothing. Conditions, Filters and Actions Of course the IsSelfHealthLow is not part of SimpleAI (although it already comes with a set of Conditions, Filters and Actions). The IsSelfHealthLow condition just checks if the health points of the NPC is under some threshold: class IsSelfHealthLow : public ai::ICondition { public: CONDITION_CLASS(IsSelfHealthLow) CONDITION_FACTORY(IsSelfHealthLow) bool evaluate(const ai::AIPtr& entity) override { const ai::Zone* zone = entity->getZone(); if (zone == nullptr) return false; const AiCharacter& chr = entity->getCharacterCast<AiCharacter>(); const auto& npc = chr.GetNpc(); if (npc.IsDead()) // Too late return false; return npc.resourceComp_.GetHealthRatio() < LOW_HP_THRESHOLD; } }; If now IsSelfHealthLow::evaluate() evaluated to true it executes the action HealSelf which is again a C++ class. It tries to find a skill that do some self healing, and uses it. For just staying alive, no Filters are needed, but for a Priest that may also heal others, Filters are need to select those Allies with low health points. Such a Filter class could look like this: void SelectLowHealth::filter(const ai::AIPtr& entity) { ai::FilteredEntities& entities = getFilteredEntities(entity); Game::Npc& chr = getNpc(entity); std::map<uint32_t, std::pair<float, float>> sorting; chr.VisitAlliesInRange(Game::Ranges::Aggro, [&](const Game::Actor* o) { if (o->resourceComp_.GetHealthRatio() < LOW_HP_THRESHOLD) { entities.push_back(o->id_); sorting[o->id_] = std::make_pair<float, float>(o->resourceComp_.GetHealthRatio(), o->GetDistance(&chr)); } }); std::sort(entities.begin(), entities.end(), [&sorting](int i, int j) { const std::pair<float, float>& p1 = sorting[i]; const std::pair<float, float>& p2 = sorting[j]; if (fabs(p1.first - p2.first) < 0.05) // If same HP (max 5% difference) use shorter distance return p1.second < p2.second; return p1.first < p2.first; }); } Now the HealOther class can get the filtered entities (which is just a std::vector<uint32_t> containing the IDs of the allies with low health points sorted by priority), and use some heal skill on that target. Conclusion SimpleAI is a great library, it is easy to integrate, easy to use and easy to configure, and as far as I can see now, it just works.

Gameplay

For the last two years I worked almost only on the foundation of this project, e.g. networking, how servers communicate, database back end, different network protocols. Now that this works more or less, I started to work on the gameplay. Party Cooperation is a big thing in this game. You meet in outposts, form a party and enter a game instance with this party and do stuff together, e.g save the world. Implementing this was quite tricky for several reasons: A lot of communication between the server and the clients is needed. Player and Party objects exist only on the game server. The clients party window is complex and needs to handle many events. There are many situations a party must behave differently. Client/Server communication First a player must invite another player. The invited player can accept or decline the invitation. If the player accepts the invitation, it will be added to the party. Of course, party members can leave the party, and party leaders (the first player in the window) has special rights. They can kick party members and invite new players. Entering a game as party As mentioned earlier, when a player enters a game, it disconnects from the game server and reconnects to it telling the server what map it wants to enter. Since party objects only exist on the game server, these are destroyed when the players disconnect. But to be able to reconstruct the party when entering the new game instance, this information must be saved. I decided to (ab)use the data server for that. Mapping The are two distinct map types: Outposts. People can meet there and make a party. Battle areas. People face foes there and fight them. You can map from one outpost to another outpost. Party members can not do that anymore, except the party leader. If other members want to map to another outpost, they must leave the party first. If there is a portal that brings players to a battle area, all members can go through it, and all members of the party will enter the same battle area instance as a party. Once in a battle area, players can not leave the party anymore, but they can map to an outpost. This will cause that these players will leave the party in the battle area. If all players of a party in a battle area type /resign they will be brought back to the last outpost. Also the leader can not kick members anymore. There are probably more edge cases I'm not aware of at the moment. Melee attacks When players are in a battle area, they can fight foes. However, identification friend or foe does not work great yet. At the moment you can attack anyone not in your party. The damage depends on many different things: The weapon that is used by the attacker Any effects that increase damage of the attacker The armor of the attacked Any effects of the target that reduces damage At the moment the damage is always 5, that's because equipment is not implemented yet, so the attacker does not have any weapon (just their fists), and the target does not have an armor. Once the target is dead, the attacker stop attacking. Now that all foes are dead, we can resign. Oh, by the way, I celebrated the 1000th commit some days ago! Next celebration is the 1337th commit 😛. Some other stats (only code I wrote, no third party libraries, STL etc.): ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- C++ 308 6804 2209 47292 C/C++ Header 395 3049 1558 18012 LESS 15 833 7 5069 Lua 74 301 329 1384 XML 12 0 1 1354 HTML 12 90 24 1032 JavaScript 1 28 24 183 DOS Batch 6 9 8 22 Markdown 3 6 0 12 CSS 1 0 7 2 ------------------------------------------------------------------------------- SUM: 827 11120 4167 74362 -------------------------------------------------------------------------------

Skills

This is an RPG and RPGs usually have skills. In this game a character can have up to eight skills. NPCs and Players usually can equip skills from the same pool. But I'm not yet sure if this game will have a PvE part, where you fight against NPCs, or if it's just a PvP game. Anyway, NPCs can have and use skills. Skills There should be different types of skills, skills that make damage to a target or make AOE damage. Other skills can heal a target or all Actors in an area, etc. All this should be controlled by the Lua script, the server itself has no knowledge what a skill does, and the Client, well, the Client knows nothing. The basic effect of a skill can be: Resurrect Heal Protect (avoid, reduce damage) Damage And a skill can have an effect to: Self Selected Target AOE So a single skill can have one or more of the above effects and one or more of the above listed targets. At the moment the skill pool is very limited, there are just three skills: Sudden Death (instant suicide): Targets self and makes damage. Instant Kill: Targets the selected target and makes damage. Instant Rezz: Targets the selected target and resurrects. All these skills are only for testing and don't make sense in a real game. Skills scripts Skills are implemented as simple Lua scripts, like bellow the "Instant Kill" skill: -- Use own include function, because the Lua search path is a mess -- and it makes it possible to precompile and cache the script include("/scripts/includes/consts.lua") include("/scripts/includes/skill_consts.lua") costEnergy = 0 activation = 0 recharge = 0 range = RANGE_MAP -- If there will be some AI then the AI must have some basic knowledge of what a skill does effect = SkillEffectDamage | SkillTargetTarget function onStartUse(source, target) if (target == nil) then -- This skill needs a target return false end if (source:GetId() == target:GetId()) then -- Can not use this skill on self return false end; if (self:IsInRange(target) == false) then -- The target must be in range return false end if (target:IsDead()) then -- Can not kill what's already dead :( return false end return true end function onEndUse(source, target) -- print("Using Instant Kill on " .. target:GetName()) return target:Die() end function onCancelUse() end Of course, this skill is a bit overpowered, but it's just for testing. Player using skills A player has a skillbar, usually at the bottom, with all equipped skills: As almost any action in this game, a hotkey can be assigned to each skill, but is also triggered when the button is clicked. NPC using skills In the same way a player uses skills, NPCs can use skills. They also have a skillbar with max. eight skills. To make this work the NPC Lua scripts have to be extended, like the Pedestrian, to make her Rezz machine: include("/scripts/includes/chat.lua") include("/scripts/includes/consts.lua") include("/scripts/includes/skill_consts.lua") name = "Pedestrian" level = 20 modelIndex = 10 -- Female Pedestrian 1 body model sex = SEX_FEMALE creatureState = CREATURESTATE_IDLE prof1Index = 3 -- Monk prof2Index = 0 -- None --behavior = "wander" local rezzTarget = nil function onInit() self:SetSpeed(0.5) -- Let's make it a rezz machine :D local skillBar = self:GetSkillBar() -- Instant rezz skill skillBar:AddSkill(9996) return true end function onUpdate(timeElapsed) if (self:IsDead() == false and rezzTarget == nil) then local actors = self:GetActorsInRange(RANGE_CASTING) for i, v in ipairs(actors) do if (v:IsDead()) then rezzTarget = v break end end if (rezzTarget ~= nil) then self:FollowObject(rezzTarget) self:Say(CHAT_CHANNEL_GENERAL, rezzTarget:GetName() .. ", you noob!") end end end function onClicked(creature) end -- self was selected by creature function onSelected(creature) self:Say(CHAT_CHANNEL_GENERAL, "Not now!") end -- creature collides with self function onCollide(creature) end function onArrived() -- self:FollowObject(rezzTarget) was called to go to the target. Now she is there. if (rezzTarget ~= nil) then local skillBar = self:GetSkillBar() local skills = skillBar:GetSkillsWithEffect(SkillEffectResurrect) if (skills[1] ~= nil) then self:SetSelectedObject(rezzTarget) self:UseSkill(skills[1]) end rezzTarget = nil end end function onEndUseSkill() rezzTarget = nil self:SetState(CREATURESTATE_IDLE) end function onStartUseSkill(skill) local targetName = "Everything!" local target = skill:GetTarget() if (target ~= nil) then targetName = target:GetName() end print("Using skill " .. skill:GetName() .. " on " .. targetName) end function onDied() self:Say(CHAT_CHANNEL_GENERAL, "Aaaaarrrrrrggghhh") end function onResurrected() self:Say(CHAT_CHANNEL_GENERAL, "Oh, ty") end This NPC will go to dead people if they are in a certain range, and will resurrect them. Video

The Game server doesn't scale vertically (i.e. throw better hardware at a problem) well, because it's mostly single-threaded, so it can host only a limited amount of concurrent games. The second option is to make it scale horizontally (throwing more hardware at a problem) well, so make it possible to spawn/shutdown Game servers dynamically across different hardware. Only the File and Game server are meant to be spawned dynamically. All other server are needed that these servers can work together. For this I made an Admin interface (btw. meanwhile I'm no longer sure what's this all about, is it a game server or a framework for scaleable servers?), which can spawn Game and File server as needed. Oh yes, I wrote a Web server for it, based on SimpleWeb which is also used for the File server, and it uses a nice Bootstrap theme (Gentelella Admin). I just added support for Cookies, Sessions and it has simple templating, because I don't want to write Markup in C++. Hell, I totally forgot how fast a Web server can be, like response times under 1ms. A central part in this process takes the Message server, which is for inter-server communication. It works like a Chat server, Clients connect to it and can broadcast messages to all other connected clients, or they can send a message to a certain client identified by its ID. A server can only spawn another instance of itself, it can not spawn a different server, like a File server can not spawn a Game server. So there is a persistent (master) server that can be told to spawn child servers, and it'll do so.
Exactly this is happening when clicking the green Spawn button. The Admin server sends a message to the Message server, which forwards the message to this particular server and this is spawning a new server of the same type with the same configuration. Well, with almost same configuration, of course it needs a unique ID (I use UUIDs), a unique Name and it must use a free Port. To get a free Port, this is what works for me: uint16_t GetFreePort() { asio::io_service service; asio::ip::tcp::acceptor acceptor(service); uint16_t port(0); asio::ip::tcp::endpoint endPoint(asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)); acceptor.open(endPoint.protocol()); acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true)); acceptor.bind(endPoint); acceptor.listen(); asio::ip::tcp::endpoint le = acceptor.local_endpoint(); port = le.port(); acceptor.close(); return port; } Shutting down a server is different, it can stop only itself. So if one server wants to shutdown another server, it sends a message to the message server, which sends the target server a message to stop itself. Even a "Master" server can not shutdown a child process, it would need to send a shutdown message to the Message server.

Migrating and Porting

Migrating the Database For some reason I thought it would be a good idea to migrate the database from MySQL to PostgreSQL. Installing PostgreSQL on Debian (in my case Debian 8 Jessie) is just one command line. I also made a simple backup script which is run by a chron job, I have a similar script for MySQL: #!/bin/sh # sudo crontab -e # Backup all PostreSQL databases every day 3 am #* 3 * * * /mnt/sda1/bak/pgsqlbak.sh TIMESTAMP=$(date +"%F") BACKUP_DIR="/mnt/sda1/bak/pgsql/$TIMESTAMP" USERNAME="my_name" export PGPASSWORD="*********" find /mnt/sda1/bak/pgsql/ -maxdepth 1 -type d -mtime +7 -exec rm -Rf {} \; mkdir -p $BACKUP_DIR databases=psql -h 127.0.0.1 -U$USERNAME -q -t -c "SELECT datname FROM pg_database" | sed -n 4,/\eof/p | grep -v rows\) | grep -v template0 | grep -v template1 | awk {'print $1'} for i in$databases; do /usr/bin/vacuumdb -z -h 127.0.0.1 -U $USERNAME$i >/dev/null 2>&1 /usr/bin/pg_dump -U $USERNAME -i -F c -b$i -h 127.0.0.1 -f $BACKUP_DIR/$i.backup /usr/bin/pg_dump -U $USERNAME -i -F p -b$i -h 127.0.0.1 -f $BACKUP_DIR/$i.sql done Migrating the database structure required some manual work, but fortunately the database is still very small. So I just used mysqldump to get a SQL file, changed some types and syntax and imported it into PostgreSQL. Because I use a caching data server, which is the only program that accesses the database, and this data server is database agnostic (so i can just switch back to MySQL with changing the configuration file), I can not use any advanced database features anyway. This made the migration of the database much easier. I can't even use auto incremented values for primary keys or other database generated values, instead it uses UUIDs as primary keys. The PostgreSQL backend was never tested before, so it surprised me a bit that the data server worked without any changes with PostgreSQL. Porting the Data server I am developing this with MSVC 2015 on Window 7 64 Bit, x86_64 architecture (I also tried VC2017 but the linker crashes all the time). The target is Debian 8 on ARMv7 (32 Bit) architecture. So, not just that the target is a different tools chain, but also the OS and architecture is different. To generate the make files (and VS solution, project files) I use premake5, because I'm not smart enough for CMake. Compiling I didn't compile anything on Linux in ages, so I thought this could be fun. Getting the source to compile with GCC and Clang was not hard, every library I use is also available for Linux, or also compiles on Linux (Lua, SQlite, Asio). What I needed to do was: Just ignore many unknown pragma warnings. Get rid of MS' secure CRT functions (e.g. sprintf_s() -> sprintf()). Get rid of the nifty #pragma comment(lib, ...) and add the lib files to the project and make files instead 😟. Throw out the ODBC database driver. I thought about also targeting MSSQL, so it seemed to be a good idea to have support for ODBC, but it's not used. Turn off Linktime Optimization (full program optimization). Took me a while until i realized my compiler does not support it. Linking So making the program just to compile was very easy, but making it also link was a pain, especially the PostgreSQL client library has many dependencies (e.g. libldap2-dev, libssl-dev, libgsasl7-dev). So I ended up having something like this in my premake5.lua: if (_TARGET_OS == "windows") then links { "abscommon", "abcrypto", "sqlite3", "libpq", "libmysql" } elseif (_TARGET_OS == "linux") then links { "pthrerad", "abscommon", "abcrypto", "sqlite3", "dl", "pq", "ssl", "crypto", "mysqlclient", "z", "gssapi_krb5" } end Finally it complied and linked, but there are still some problems as you can see on the screenshot. It runs now on the same machine as the database server. The data server is very lightweight (without load 🤣😞 Update The linking issues have been solved with upgrading VS2017 to version 15.8.6. Now everything (18 projects) compile, link and run fine with VS2017. Also the obvious bugs on Linux have been solved, but the Linux version of the data server is still not reliable.

Part 4: Portals and UI changes

Portals In games you find something that take you to other maps, sometimes this is called a Portal. On the server side Portals are just scriptable objects like NPCs. The game script adds Portals like it would add an NPC: -- Game start up function onStart() local portal = self:AddNpc("/scripts/creatures/logic/portal.lua") if (portal ~= nil) then -- Map ID where this portal leads to portal:SetVarString("destination", "75e3dfcf-479a-11e8-ad09-02100700d6f0") -- Will call onTrigger() when it collides portal:SetTrigger(true) -- Set position of portal local x = -20.059 local z = -0.00870347 local y = 26.7 portal:SetPosition(x, y, z) end -- ... end The script for the portal is similar to a script for an NPC: name = "Temple of Athene" level = 20 modelIndex = 11 sex = 0 creatureState = 1 prof1Index = 0 prof2Index = 0 function onInit() -- Player collides with BB. Make it a bit larger than the default BB. self:SetBoundingBox(-1, -1, -1, 1, 1, 1) return true end function onTrigger(creature) local player = creature:AsPlayer() if (player ~= nil) then player:ChangeGame(self:GetVarString("destination")) end end The process for changing a game instance is a bit complicated. The player is only connected to the game server when he/she is in a game instance. When you click on a map in the map window the client disconnects from the game server and connects to it again, telling the game server which map it would like to access. The game server creates an instance of this map, and adds the player to this instance. A Portal is a bit different, because the server initiates the switch to a different map. Also we must distinguish whether the client enters an existing instance—which is always the case when using the map window and the instance is not full, because these are outposts where people can meet—or the server creates a new instance. This is the case when entering a battle field, which is exclusive for a party or two, e.g. a PvP combat. But when it comes to gameplay and game mechanics, nothing has been implemented yet. Video UI changes Several small changes to the UI were made. The people from Urho3D have updated the UI texture and styles, which I really like, it is so friendly and ... green xD. A big Thank You to the Urho3D developers for this great library! Additionally the position of the health bar above the characters looks now more natural and speech bubbles have been added.

Part 3: UI and Network

Preface Sorry for the weird order, this is partly chronological and partly what came out of my mind. UI I'm not really a frontend guy, give me some backend service nobody will ever look at, and I'm happy. But since RPGs usually have a rich GUI (Chat, Party, some kinds of Maps, Options etc.) and I'm doing it alone, I'll have to do it. After messing around a lot with Urho3D's UI, I think slowly I get used to it. I even tried Nuklear but realized it's not what I needed due to its immediate mode nature, and the code ends up to be a bit spaghettiish. With Urho3D I can create the UI with the Editor, save it as XML file and load it at runtime, subscribe to some events and it's done. It's even possible to combine several windows into one complex window. For example, the Options window consists of six windows: The container with just the tabs (taken from Urho3D-UI-Components) and each tab is a separate window. So far we have: a fully working Chat window with the different channels, a partly working Options window with fully customizeable shortcuts and multiple shortcuts per action, a Party window and Mission Map that's not working at all, a fully working Mail window and compose New Mail window (except that the multiline edit still bothers me a lot), a fully working Game menu, something that shows the Ping to the server and other things. Network Some details about the network layer. As mentioned earlier this is not a fast game, like an action game or a shooter. That's why I chose TCP over UDP. Using TCP takes out a lot of problems, like packet losses, wrong order of packets. With TCP, packets are always received in the right order, or something goes terribly wrong, e.g. disconnects. Of course this comes at a price (no advantage without disadvantage!), higher latency. But for slow games it may not matter so much. Update rate You know that interactive programs execute an endless loop. An interactive console program could look like this: void main() { bool running = true; while (running) { std::string input; std::getline(std::cin, input); if (input.compare("quit") == 0) running = false; else if ... } } A Windows program looks usually like this: void main() { MSG msg = { 0 }; while (msg.message != WM_QUIT) { if (::PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessageW(&msg); } } } A game isn't much different: void main() { while (the_game_is_running) { // Update game state Update(); // Render the frame Render(); // Wait to meet the desired frame rate. // If vertical synchronization is on, the Graphics API may do this. WaitSomeTime(); } } This game server does it similar, the games have an Update() method which is executed from time to time. The difference is that a game server does not update the game state in an endless loop and waits most of the time, but it uses a Scheduler. It would be a waste of time when it sleeps most of the time, it can simulate other games (or do something completely different). The game update method of this game server looks a bit like this (simplified): void Game::Update() { // Dispatcher Thread int64_t tick = Utils::AbTick(); if (lastUpdate_ == 0) lastUpdate_ = tick - NETWORK_TICK; uint32_t delta = static_cast<uint32_t>(tick - lastUpdate_); lastUpdate_ = tick; // First Update all objects for (const auto& o : objects_) { o->Update(delta); } // Send game status to players SendStatus(); // Schedule next update const int64_t end = Utils::AbTick(); const uint32_t duration = static_cast<uint32_t>(end - lastUpdate_); // At least SCHEDULER_MINTICKS const int32_t sleepTime = std::max<int32_t>(SCHEDULER_MINTICKS, NETWORK_TICK - duration); Asynch::Scheduler::Instance.Add( Asynch::CreateScheduledTask(sleepTime, std::bind(&Game::Update, this)) ); } The server runs the simulations every 50ms (NETWORK_TICK = 50), this are 20 simulations per second. The client runs updates every ~16ms (60FPS) or ~8ms (140FPS) or whatever frame rate it is running. The client sends all collected packets (e.g. player inputs) all 16ms (60 times per second) to the server, no matter if it runs at a higher frame rate (lower frame rates are a problem at the moment, that needs to be addressed...). The server collects all this inputs from the client and puts it into a queue, which is processed in the next update to calculate the next game state. Delta compression Delta compression means the server does not always send the whole game state to all clients, but only what has changed since the last state. This reduces bandwidth usage drastically. Since we use TCP we can assume that the client always knows the previous game state, so it's safe to send only the difference. This is the only compression it uses. The packets are small binary packets and may not compress well, so it's probably not worth the CPU cycles. Client/server asynchronicity There are mainly three methods to address the problem that client and server are asynchronous, which is caused by different update rates (20 updates/sec on server, 60 updates/sec on client), network latency, server load etc.: Entity interpolation is already done with Entity Position Interpolation. Input prediction is not yet done. Lag compensation/Server reconciliation will not be done. This is more important for fast games. Further reading In no particluar order. https://gafferongames.com/ http://ithare.com/mmog-world-states-and-reducing-traffic/ https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking

Part 2: Players, NPCs and Navigation

Player Replaced the ball with a real character model. At the moment I have just a few character models with two animations (run and idle): Video The character was made with MakeHuman and the animations are from Mixamo. The workflow is straight forward and (more or less) painless: In MakeHuman export the character as .mhx2. Import in Blender. The MakeHuman Blender Plugin has a MHX2 importer. If needed apply transformation, but if you use MHX2 i think this is not needed. Export from Blender as .fbx. Upload the FBX file to Mixamo. Select the animation. Download the FBX file. Import in Blender. Export the character with the Urho3D-Blender plugin. I would recommend to export a Prefab so you can load the whole thing with materials etc. at once. NPCs RPGs (and other games) usually have NPCs - non playing characters, well they are playing but they are computer controlled. This game should be largely scriptable. There are different game formats and maps and each should just differ in the script that is executed. A game script could look like this: function onStart() -- self is the this pointer -- Spawn an NPC and set the position. local smith = self:AddNpc("/scripts/creatures/npcs/smith.lua") if (smith ~= nil) then local x = -6.71275 local z = 12.5906 local y = self:GetTerrainHeight(x, z) smith:SetPosition(x, y, z) -- Here we just use the Y-axis, internally it uses Quaternions smith:SetRotation(180) end local merchant = self:AddNpc("/scripts/creatures/npcs/merchant.lua") if (merchant ~= nil) then local x = 4.92965 local z = 12.2049 local y = self:GetTerrainHeight(x, z) merchant:SetPosition(x, y, z) merchant:SetRotation(180) end end function onStop() end function onAddObject(object) print("Object added: " .. object:GetName()) end function onRemoveObject(object) print("Object removed: " .. object:GetName()) end function onPlayerJoin(player) player:AddEffect(empty, 1000, 0) print("Player joined: " .. player:GetName()) end function onPlayerLeave(player) player:RemoveEffect(1000) print("Player left: " .. player:GetName()) end -- Game Update function onUpdate(timeElapsed) -- print(timeElapsed) end As with other scripting APIs, there are events that are called by the program (from C++), and there are objects that have methods accessible by the script. An NPC script is as simple: -- Object stats/properties name = "Smith" level = 20 modelIndex = 5 -- Smith body model sex = 2 -- Male creatureState = 1 -- Idle prof1Index = 1 -- Warrior prof2Index = 0 -- None function onInit() return true end function onUpdate(timeElapsed) -- Do something intelligent! end -- self was selected by creature function onSelected(creature) print(creature:GetName() .. " selected me, the " .. self:GetName() .. " :D") end -- creature collides with self function onCollide(creature) -- Testing Octree query local objects = self:QueryObjects(2.0) print(type(objects)) for i, v in ipairs(objects) do print(i, v, v:GetName()) end end Navigation Navigation is a difficult topic. Fortunately there is Recast & Detour, a library to find paths with a navigation mesh. Currently it can to go to a static position and follow a moving object. Technically it's the same, but it doesn't recalculate the path when the object is not moving. However, I've read somewhere that Detour can calculate incomplete paths which would be useful for following objects, but I didn't figure that out yet. Video Detour uses navigation meshes created with Recast. The image bellow shows the generated navigation mesh based on the heightfield of this map. As mentioned earlier, the heightfield is just a bitmap where brighter colors are higher altitudes than darker colors. A mesh is created with a custom command line program from this bitmap, which Recast uses to generate the navigation mesh. To be continued...

Introduction

A game server written in C++ from scratch. The game client uses Urho3D. Current Features Uses a single TCP stream for the game protocol. This is not a shooter or action game, more like a classical online RPG. Database back-end is currently MySQL (PostgresSQL and ODBC is also implemented). Instanced world. Encrypted Game and Login Protocol. Spawn any number of server which may have heavy load (game, file server) even on different hardware. Local (map), Guild chat and whisper is currently working, even across different game server. Static objects are directly loaded from Urho3D's scene files. Navigation using Recast/Detour. ETA In 100 years. Credits Server Asio Lua Kaguya Lua C++ Binding pugixml Recast & Detour Client Urho3D Asio Entity Position Interpolation Terrain I have been playing around with terrains and heightmaps. Creating heightmaps from bitmaps is very easy, color values basically correspond to altitude values. So you can generate an array with width * height elements with the height values, and with some simple calculations you get for every possible world position (x, z) the height (y). But at the moment it seems the server heightmap is slightly different to what Urho3D generates ;(: Video Finally this is fixed, the character sticks now exactly to the terrain. Cluster What we see here is a ABx server cluster. Since the server is built as a set of micro services it is easy to make a cluster. The screenshot shows three clients where one is connected to a different game server (Sun Shine) and whisper (blue) and guild (green) chat is still working (cross server chat). All the communication is done via TCP/IP so each server can be on different hardware. The login server is also some sort of load balancer since it redirects the game clients automatically to the game server with the least load. Data Server The Data server provides data from the database server and caches it. It also acts as thread-safe inter-server shared memory. There can be only one data server. It is the central point to which all other servers connect, and get their data from. This makes it possible to spawn any number of game server and they all share the same data. File Server The file server is a simple HTTP server providing files and other information. The client may connect to it from time to time and download data. There can be any number of file servers. Usually you may want to have file servers in different regions, and not on the same machine. A file server does not need much resources, just bandwidth. Login Server Used by the client to login, create accounts and manage characters. It also tells the client to which Game and File Server to connect. The client can query this server for available game server so it can connect "manually" to a certain game server. There can be only one login server, since all clients connects to this server. However, there is an optional TCP (Layer 4) load balancer/proxy server (ablb), which makes it possible to have several Login Servers. Once the client is authenticated, the connection to the login server is closed. Game Server The game server simulates the games. There can be any number of game servers, but if they run on the same machine, the game servers must listen on different ports. Since all game server connect to the same data server, all game server share the same data. Because this game is designed to have an instanced world, several game server instances appear to the player as one game server. It is even possible to change the game server with a simple menu click. Message Server This one is for inter server communication, for example to deliver messages from a player to another player on a different game server. It is also used to push notifications (e.g new mail) to the player.