aesop-server/lib/srv-game-logic/srv-game-logic.cpp

Go to the documentation of this file.
00001 /*
00002  * srv-game-logic.cpp
00003  *
00004  * Copyright (C) 2008,2009  Thomas A. Vaughan
00005  * All rights reserved.
00006  *
00007  *
00008  * Redistribution and use in source and binary forms, with or without
00009  * modification, are permitted provided that the following conditions are met:
00010  *     * Redistributions of source code must retain the above copyright
00011  *       notice, this list of conditions and the following disclaimer.
00012  *     * Redistributions in binary form must reproduce the above copyright
00013  *       notice, this list of conditions and the following disclaimer in the
00014  *       documentation and/or other materials provided with the distribution.
00015  *     * Neither the name of the <organization> nor the
00016  *       names of its contributors may be used to endorse or promote products
00017  *       derived from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THOMAS A. VAUGHAN ''AS IS'' AND ANY
00020  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00021  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00022  * DISCLAIMED. IN NO EVENT SHALL THOMAS A. VAUGHAN BE LIABLE FOR ANY
00023  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00024  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00026  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00027  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00028  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00029  *
00030  *
00031  * Reference implementation of an object that contains server-side game logic.
00032  */
00033 
00034 // includes -------------------------------------------------------------------
00035 #include "srv-game-logic.h"             // always include our own header 1st!
00036 
00037 #include "datahash/datahash_util.h"
00038 #include "map-dynamics/map-dynamics.h"
00039 #include "aesop-srv/srv-object.h"
00040 #include "threadsafe/smart_mutex.h"
00041 #include "timer/timer.h"
00042 
00043 
00044 namespace aesop {
00045 
00046 
00047 ////////////////////////////////////////////////////////////////////////////////
00048 //
00049 //      static helper methods
00050 //
00051 ////////////////////////////////////////////////////////////////////////////////
00052 
00053 static float
00054 getRandomX
00055 (
00056 void
00057 )
00058 throw()
00059 {
00060         static const float s_maxX = 0.25;
00061         float x = (1.0 * rand()) / RAND_MAX;    // 0 <= x <= 1
00062         x -= 0.5;                               // -0.5 <= x <= 0.5
00063         x *= 2 * s_maxX;                        // -s_maxX <= x <= s_maxX
00064 
00065         return x;
00066 }
00067 
00068 
00069 
00070 static const char *
00071 getIntBuffer
00072 (
00073 IN dword_t dw
00074 )
00075 {
00076         static char s_buffer[64];
00077         sprintf(s_buffer, "%lu", (long) dw);
00078         return s_buffer;
00079 }
00080 
00081 
00082 
00083 static bool
00084 isPortal
00085 (
00086 IN smart_ptr<Instance>& i
00087 )
00088 {
00089         ASSERT(i, "null");
00090 
00091         smart_ptr<Datahash> data = i->getInstanceData("game");
00092         if (!data)
00093                 return false;   // nope
00094 
00095         return !strcmp("portal", getOptionalString(data, "type", ""));
00096 }
00097 
00098 
00099 class DestroyObjectTimer : public timer::Timer {
00100 public:
00101         ~DestroyObjectTimer(void) throw() { }
00102 
00103         // timer::Timer class interface methods --------------------------------
00104         void notifyTimer(void) {
00105                         DPRINTF("removing object %ld", m_objectId);
00106 
00107                         smart_ptr<PhysicsObject> obj =
00108                             getPhysicsObjectById(m_objectId);
00109                         if (!obj) {
00110                                 DPRINTF("Object does not exist: %ld",
00111                                     m_objectId);
00112                                 DPRINTF("  Ignoring remove request");
00113                                 return;
00114                         }
00115 
00116                         MapDynamics * dyn =
00117                             getMapDynamicsFromPhysicsObject(obj);
00118                         if (!dyn) {
00119                                 DPRINTF("ERROR: cannot get map from object?");
00120                                 return;
00121                         }
00122                         dyn->removeObject(obj);
00123                 }
00124 
00125         // static factory methods ----------------------------------------------
00126         static smart_ptr<timer::Timer> create(IN long objectId) {
00127                         smart_ptr<DestroyObjectTimer> local =
00128                             new DestroyObjectTimer;
00129                         ASSERT(local, "out of memory");
00130 
00131                         local->m_objectId = objectId;
00132 
00133                         return local;
00134                 }
00135 
00136 private:
00137         DestroyObjectTimer(void) throw() { }
00138 
00139         // private member data -------------------------------------------------
00140         long                    m_objectId;
00141 };
00142 
00143 
00144 
00145 /// \ingroup srv_game_logic_impl
00146 ///
00147 /// This is a reference implementation of a \ref aesop::ServerGameLogic object.
00148 ///
00149 /// \n
00150 /// In most cases, game creators will want to create their own game logic!
00151 /// But this can be used as a starting point.
00152 ///
00153 /// \b TODO: make this a visible class?  At the moment, it is an implementation-
00154 /// only class hidden in a .cpp file.  I could make it public so people could
00155 /// inherit from it.  However, I suspect that game logic is complex enough
00156 /// that people will either roll their own entirely, or use composition rather
00157 /// than inheritance.
00158 class RefGameLogic : public ServerGameLogic {
00159 public:
00160         // constructor, destructor ---------------------------------------------
00161         ~RefGameLogic(void) throw() { }
00162 
00163         // public class methods ------------------------------------------------
00164         void initialize(void); 
00165 
00166         // aesop::ServerGameLogic class interface methods ----------------------
00167         void setPlayerManager(IN smart_ptr<PlayerManager>& playerMgr);
00168         void setMapManager(IN smart_ptr<MapManager>& mapMgr); 
00169         bool requestPlayerStartTS(IO player_rec_t& pr);
00170         void tick(IN float dt);
00171         void parseGameData(IN conn_id_t conn_id, IN xdrbuf::Input * input);
00172 
00173 private:
00174         // private helper methods ----------------------------------------------
00175         void handleCollision(IN PhysicsWorld * world,
00176                                 IN smart_ptr<PhysicsObject> obj1,
00177                                 IN smart_ptr<PhysicsObject> obj2);
00178         void handlePortal(IN PhysicsWorld * world,
00179                                 IN smart_ptr<Instance>& player,
00180                                 IN smart_ptr<Instance>& portal);
00181 
00182         // private member data -------------------------------------------------
00183         smart_mutex                     m_mutex;
00184         smart_ptr<PlayerManager>        m_playerMgr;
00185         smart_ptr<MapManager>           m_mapMgr;
00186         smart_ptr<timer::Queue>         m_timerQueue;
00187         std::string                     m_mostRecentMapId;
00188         qword_t                         m_currentTime;  // microseconds!
00189 };
00190 
00191 
00192 ////////////////////////////////////////////////////////////////////////////////
00193 //
00194 //      RefGameLogic -- public class methods
00195 //
00196 ////////////////////////////////////////////////////////////////////////////////
00197 
00198 void
00199 RefGameLogic::initialize
00200 (
00201 void
00202 )
00203 {
00204         m_timerQueue = timer::Queue::create();
00205         ASSERT(m_timerQueue, "null");
00206 }
00207 
00208 
00209 
00210 ////////////////////////////////////////////////////////////////////////////////
00211 //
00212 //      RefGameLogic -- aesop::GameLogic class interface methods
00213 //
00214 ////////////////////////////////////////////////////////////////////////////////
00215 
00216 void
00217 RefGameLogic::setPlayerManager
00218 (
00219 IN smart_ptr<PlayerManager>& playerMgr
00220 )
00221 {
00222         ASSERT(playerMgr, "null");
00223 
00224         // lock
00225         m_playerMgr = playerMgr;
00226 }
00227 
00228 
00229 
00230 void
00231 RefGameLogic::setMapManager
00232 (
00233 IN smart_ptr<MapManager>& mapMgr
00234 )
00235 {
00236         ASSERT(mapMgr, "null");
00237 
00238         // stash the pointer
00239         m_mapMgr = mapMgr;
00240 }
00241 
00242 
00243  
00244 bool
00245 RefGameLogic::requestPlayerStartTS
00246 (
00247 IO player_rec_t& pr
00248 )
00249 {
00250         ASSERT(pr.udpConnId, "null");
00251         ASSERT(pr.playerId > 0, "bad player id: %d", pr.playerId);
00252         ASSERT(m_mapMgr, "null");
00253 
00254         DPRINTF("Requesting start for player %d, user %s",
00255             pr.playerId, (const char *) pr.username);
00256 
00257         // validate player record
00258         if (!pr.isAuthenticated()) {
00259                 DPRINTF("Shouldn't be told about unauthenticated players!");
00260                 return false;
00261         }
00262 
00263         // is player already playing?
00264         if (pr.obj) {
00265                 DPRINTF("Player is already in world");
00266                 return false;
00267         }
00268 
00269         // verify recent map state
00270         if ("" == m_mostRecentMapId) {
00271                 DPRINTF("No recent map!");
00272                 return false;
00273         }
00274 
00275         smart_ptr<MapDynamics> dyn = m_mapMgr->getMap(m_mostRecentMapId.c_str());
00276         if (!dyn) {
00277                 DPRINTF("Most recently loaded map is no longer relevant");
00278                 m_mostRecentMapId = "";
00279                 return false;
00280         }
00281 
00282         // does the player know where they *want* to go?
00283         if (!pr.mapId) {
00284                 // no clue!  Start them on most recent map
00285                 pr.mapId.set(m_mostRecentMapId.c_str());
00286         }
00287 
00288         // okay, grab that map
00289         dyn = m_mapMgr->getMap(pr.mapId);
00290         if (!dyn)
00291                 return true;    // we got this far
00292         Map * map = dyn->getMap();
00293         ASSERT(map, "null");
00294 
00295         // do they have the starting point?
00296         if (!pr.startId) {
00297                 pr.startId.set(map->getDefaultStartingPointId());
00298         }
00299 
00300         // load requested starting point
00301         destination_t dest;
00302         map->getStartingPoint(pr.startId, dest);
00303 
00304         // get mid point for now
00305         point3d_t mid;
00306         mid.x = 0.5 * (dest.rect.x0 + dest.rect.x1);
00307         mid.y = 0.5 * (dest.rect.y0 + dest.rect.y1);
00308         mid.z = 0.5 * (dest.rect.z0 + dest.rect.z1);
00309 
00310         // create datahash to store our rules data
00311         // TODO: get our own custom struct for this!
00312         smart_ptr<Datahash> data = Datahash::create();
00313         data->insert("type", "player");
00314         data->insert("udpConnId", getIntBuffer(pr.udpConnId));
00315         data->insert("playerId", getIntBuffer(pr.playerId));
00316 
00317         // create instance for player
00318         smart_ptr<Instance> instance = Instance::create(pr.playerTag, "player");
00319         placement_t& p = instance->getPlacement();
00320         p.position = mid;
00321         instance->setInstanceData("game", data);
00322         instance->dump("Just added player here");
00323 
00324         // add to map
00325         pr.obj = dyn->addInstance(instance);
00326         if (!pr.obj) {
00327                 DPRINTF("Nothing added?");
00328         }
00329 
00330         // all done
00331         return true;    // ask server to update player information
00332 }
00333 
00334 
00335 
00336 void
00337 RefGameLogic::tick
00338 (
00339 IN float dt
00340 )
00341 {
00342         ASSERT(dt > 0.0, "bad time delta: %f", dt);
00343         ASSERT(m_timerQueue, "null");
00344 
00345         // update time
00346         m_currentTime += (qword_t) (1.0e6 * dt + 0.5);
00347 
00348         // check all timers
00349         m_timerQueue->checkTimers(m_currentTime);
00350 
00351         // don't go any further without maps
00352         if (!m_mapMgr)
00353                 return;
00354 
00355         // loop through maps, looking for collisions
00356         MapManager::iterator_t iter;
00357         m_mapMgr->getIterator(iter);
00358         smart_ptr<MapDynamics> dyn;
00359         while (m_mapMgr->getNextMap(iter, dyn)) {
00360                 ASSERT(dyn, "null dynamics");
00361 
00362                 // need to set map id?
00363                 if ("" == m_mostRecentMapId) {
00364                         Map * map = dyn->getMap();
00365                         ASSERT(map, "null");
00366                         m_mostRecentMapId = map->getId();
00367                 }
00368 
00369                 // loop through collisions
00370                 PhysicsWorld * world = dyn->getPhysics();
00371                 ASSERT(world, "null");
00372                 PhysicsWorld::collision_iterator_t ci;
00373                 world->getCollisionIterator(ci);
00374                 PhysicsWorld::collision_record_t cr;
00375                 while (world->getNextCollision(ci, cr)) {
00376                         this->handleCollision(world, cr.obj1, cr.obj2);
00377                 }
00378         }
00379 }
00380 
00381 
00382 
00383 void
00384 RefGameLogic::parseGameData
00385 (
00386 IN conn_id_t conn_id,                   // connection ID of source
00387 IN xdrbuf::Input * input
00388 )
00389 {
00390         // ASSERT(conn_id, "null");
00391         ASSERT(input, "null");
00392 
00393         // the server has encountered game data in an incoming UDP packet.
00394         // we can parse it here.
00395 
00396         // keep parsing until we encounter a closing packlet.
00397         // TODO: clean up this parsing contract
00398 
00399         while (true) {
00400                 xdrbuf::PackletHeader ph = input->getNextPackletHeader();
00401                 xdrbuf::ePackletType type = ph.getType();
00402                 if (xdrbuf::ePacklet_ParentEnd == type)
00403                         break;          // end of game data
00404 
00405                 char name = ph.getName();
00406                 switch (name) {
00407 
00408                 case 'a':
00409                         {
00410                                 // animation!
00411                                 // first child packlet: player ID
00412                                 ph = input->getNextPackletHeader();
00413                                 int32_t l;
00414                                 input->readInt32s(&l, 1);
00415                                 int playerId = (int) l;
00416 
00417                                 // second child packlet: animation state
00418                                 ph = input->getNextPackletHeader();
00419                                 const int bufsize = 128;
00420                                 char buffer[bufsize];
00421                                 int nChars = ph.getDataCount();
00422                                 ASSERT_THROW(nChars < bufsize,
00423                                     "reading animation string of size " <<
00424                                     nChars);
00425                                 input->readString(buffer, nChars);
00426                                 buffer[nChars] = 0;     // null terminate
00427                                 DPRINTF("Just read animation state: '%s'",
00428                                     buffer);
00429 
00430                                 // now make sure the parent packlet is closed
00431                                 ph = input->getNextPackletHeader();
00432                                 if (xdrbuf::ePacklet_ParentEnd != ph.getType()) {
00433                                         WAVE_EX(wex);
00434                                         wex << "Animation packlet not closed";
00435                                 }
00436 
00437                                 player_rec_t pr;
00438                                 if (!m_playerMgr->getPlayerByHostAndPlayerId(conn_id,
00439                                       playerId, pr)) {
00440                                         DPRINTF("No such player for animation");
00441                                 } else {
00442                                         if (!pr.obj) {
00443                                                 DPRINTF("No object for player?");
00444                                         } else {
00445                                                 smart_ptr<Instance> inst =
00446                                                     getInstanceFromPhysicsObject(pr.obj);
00447                                                 if (!inst) {
00448                                                         DPRINTF("No instance?");
00449                                                 } else {
00450                                                         smart_ptr<ServerObject> so =
00451                                                             getServerObject(inst);
00452                                                         ASSERT_THROW(so, "null");
00453                                                         so->setAnimationState(conn_id, buffer);
00454                                                 }
00455                                         }
00456                                 }
00457 
00458                         }
00459                         break;
00460 
00461                 case 's':
00462                         {
00463                                 // shooting!
00464 
00465                                 // first child packlet: player ID
00466                                 ph = input->getNextPackletHeader();
00467                                 int32_t l;
00468                                 input->readInt32s(&l, 1);
00469                                 int playerId = (int) l;
00470                                 DPRINTF("Shot request from player: %d", playerId);
00471                                 if (playerId < 1) {
00472                                         WAVE_EX(wex);
00473                                         wex << "Bad player id: " << playerId;
00474                                 }
00475 
00476                                 // second child packlet: orientation
00477                                 ph = input->getNextPackletHeader();
00478                                 float f[7];
00479                                 input->readFloats(f, 7);
00480 
00481                                 point3d_t pos;
00482                                 pos.x = f[0];
00483                                 pos.y = f[1];
00484                                 pos.z = f[2];
00485 
00486                                 quaternion_t rot;
00487                                 rot.x = f[3];
00488                                 rot.y = f[4];
00489                                 rot.z = f[5];
00490                                 rot.w = f[6];
00491 
00492                                 DPRINTF("Got shot request");
00493                                 pos.dump("  position");
00494                                 rot.dump("  rotation");
00495 
00496                                 // now make sure the parent packlet is closed
00497                                 ph = input->getNextPackletHeader();
00498                                 if (xdrbuf::ePacklet_ParentEnd != ph.getType()) {
00499                                         WAVE_EX(wex);
00500                                         wex << "Shot packlet not closed";
00501                                 }
00502 
00503                                 // what map (if any) is the player in right now?
00504                                 player_rec_t pr;
00505                                 if (conn_id &&
00506                                     m_playerMgr->getPlayerByHostAndPlayerId(conn_id, playerId, pr)) {
00507                                         DPRINTF("Found player!");
00508                                         DPRINTF("  In map: %s", (const char *) pr.mapId);
00509 
00510                                         smart_ptr<MapDynamics> dyn = m_mapMgr->getMap(pr.mapId);
00511                                         if (dyn) {
00512                                                 DPRINTF("  have map dynamics!");
00513 
00514                                                 // which direction?
00515                                                 point3d_t dir;
00516                                                 getVectorFromQuaternion(rot, dir);
00517 
00518                                                 // start a bit in that direction
00519                                                 smart_ptr<Instance> inst =
00520                                                     Instance::create(NULL, "25cm-cube");
00521                                                 placement_t& p = inst->getPlacement();
00522                                                 p.position = pos + dir;
00523                                                 p.rotation = rot;
00524 
00525                                                 smart_ptr<PhysicsObject> obj = dyn->addInstance(inst);
00526                                                 if (obj) {
00527                                                         DPRINTF("  Added object!");
00528                                                 }
00529 
00530                                                 // set up linear velocity
00531                                                 obj->setLinearVelocity(7.5 * dir);
00532 
00533                                                 // add a random angular velocity
00534                                                 point3d_t angular;
00535                                                 angular.x = getRandomX();
00536                                                 angular.y = getRandomX();
00537                                                 angular.z = getRandomX();
00538                                                 obj->setAngularVelocity(angular);
00539 
00540                                                 // set up a timer to destroy it later
00541                                                 long objectId = obj->getId();
00542                                                 smart_ptr<timer::Timer> timer =
00543                                                     DestroyObjectTimer::create(objectId);
00544                                                 ASSERT(timer, "null");
00545 
00546                                                 qword_t delay = 15 * 1000 * 1000;// 15s
00547                                                 m_timerQueue->addTimer(delay, timer);
00548                                         }
00549                                 }
00550 
00551                                 // add 25cm cube for now...
00552 
00553                         }
00554                         break;
00555 
00556                 default:
00557                         {
00558                                 WAVE_EX(wex);
00559                                 wex << "Unknown packlet type: " << name;
00560                         }
00561                 }
00562         }
00563 }
00564 
00565 
00566 
00567 ////////////////////////////////////////////////////////////////////////////////
00568 //
00569 //      RefGameLogic -- private helper methods
00570 //
00571 ////////////////////////////////////////////////////////////////////////////////
00572 
00573 void
00574 RefGameLogic::handleCollision
00575 (
00576 IN PhysicsWorld * world,
00577 IN smart_ptr<PhysicsObject> obj1,
00578 IN smart_ptr<PhysicsObject> obj2
00579 )
00580 {
00581         ASSERT(world, "null");
00582         ASSERT(obj1, "null");
00583         ASSERT(obj2, "null");
00584 
00585         smart_ptr<Instance> i1 = getInstanceFromPhysicsObject(obj1);
00586         smart_ptr<Instance> i2 = getInstanceFromPhysicsObject(obj2);
00587 
00588         DPRINTF("instance %s (type %s) hit instance %s (type %s)",
00589             i1->getInstanceId(), i1->getTypeId(),
00590             i2->getInstanceId(), i2->getTypeId());
00591 
00592         bool player1 = !strcmp("player", i1->getTypeId());
00593         bool player2 = !strcmp("player", i2->getTypeId());
00594 
00595         bool portal1 = isPortal(i1);
00596         bool portal2 = isPortal(i2);
00597 
00598         if (player1 && portal2) {
00599                 this->handlePortal(world, i1, i2);
00600         } else if (player2 && portal1) {
00601                 this->handlePortal(world, i2, i1);
00602         }
00603 }
00604 
00605 
00606 
00607 void
00608 RefGameLogic::handlePortal
00609 (
00610 IN PhysicsWorld * world,
00611 IN smart_ptr<Instance>& player,
00612 IN smart_ptr<Instance>& portal
00613 )
00614 {
00615         ASSERT(world, "null");
00616         ASSERT(player, "null");
00617         ASSERT(portal, "null");
00618         ASSERT(m_playerMgr, "null");
00619         ASSERT(m_mapMgr, "null");
00620 
00621         // first, crack open player
00622         const Datahash * pdata = player->getInstanceData("game");
00623         if (!pdata) {
00624                 DPRINTF("Player instance has no game logic data?");
00625                 return;
00626         }
00627         if (strcmp("player", getOptionalString(pdata, "type", ""))) {
00628                 DPRINTF("type != player?");
00629                 return;
00630         }
00631         conn_id_t udpConnId = (conn_id_t) getLong(pdata, "udpConnId");
00632         int playerId = (int) getLong(pdata, "playerId");
00633 
00634         DPRINTF("Player hit portal: from host 0x%08lx, player id %d",
00635             (long) udpConnId, playerId);
00636 
00637         player_rec_t pr;
00638         if (!m_playerMgr->getPlayerByHostAndPlayerId(udpConnId, playerId, pr)) {
00639                 DPRINTF("Could not find player?");
00640                 return;
00641         }
00642         DPRINTF("  username = %s", (const char *) pr.username);
00643 
00644         // now, crack open portal data
00645         const Datahash * odata = portal->getInstanceData("game");
00646         if (!odata) {
00647                 DPRINTF("Portal instance has no game logic data?");
00648                 return;
00649         }
00650         if (strcmp("portal", getOptionalString(odata, "type", ""))) {
00651                 DPRINTF("type != portal?");
00652                 return;
00653         }
00654         const char * mapId = getString(odata, "mapId");
00655         const char * startId = getString(odata, "startId");
00656         DPRINTF("requesting map '%s', start '%s'", mapId, startId);
00657 
00658         // okay, reset player!
00659         if (pr.obj) {
00660                 world->removeObject(pr.obj);
00661                 pr.obj = NULL;
00662         }
00663         pr.mapId.set(mapId);
00664         pr.startId.set(startId);
00665         m_playerMgr->updatePlayer(pr);
00666 
00667         // remember this!
00668         m_mostRecentMapId = mapId;
00669 
00670         // kick off asynchronous load (this will no-op if already loaded)
00671         m_mapMgr->requestLoad(mapId);
00672 }
00673 
00674 
00675 
00676 ////////////////////////////////////////////////////////////////////////////////
00677 //
00678 //      public API
00679 //
00680 ////////////////////////////////////////////////////////////////////////////////
00681 
00682 smart_ptr<ServerGameLogic>
00683 createServerGameLogic
00684 (
00685 void
00686 )
00687 {
00688         smart_ptr<RefGameLogic> rp = new RefGameLogic;
00689         ASSERT(rp, "out of memory");
00690 
00691         rp->initialize();
00692 
00693         return rp;
00694 }
00695 
00696 
00697 };      // aesop namespace
00698