summaryrefslogblamecommitdiff
path: root/src/game-server/state.cpp
blob: 3903d93ebc6018cf17bcf81c38e5c6b6b5f98bcc (plain) (tree)






















                                                                             

                  
                    
                  
                                            
                                      
                               
                              
                                       


                                     
                         
 
                                        
 
                               


                                                                   
     

                       
 



                                                                    
                                                    
         
                                                          
         






                                                                    
 
                             








                                                                    
                                


                  
                                                         





                                                              
                                                                        
                                                                                         

                             
 



                                                                  
                                                               
 
                          
         
                                    
                                                           




                                                         
 
















                                                                            

                                    
             







                                                           
             

         
                                                                      
                                                               
                                                                        
 

                                      
         

                                       
             


                                                                     
                                     
 


                                                   


                                                                      
                            
                                      
                 
                                                                 












                                                                   
                     








                                                   
                              



                                                  
 

                                                                     
 
                                         
                         

                                     
                                                    








                                                            
 












                                                               
 


                                                             
 

                                          
 






                                                                   
                                    
                                                                                        






                                                               
                                                                        


                                        
                                               












                                                                                           





                                                             
 
 



                           

                    



                          
                                          

                                                                                                

                                      




                             
                       
 
                                                                     

                                  

         
                                                                  
         

                                  
                              


                                                            
         
     
 



                           






                                                                           
         
                              
             
                          
                                                     
                 
                                                                     





                              
                          


                            
             
                          
                                    
                                 
                                    
 

                                                             
                                                                         
                                                                                  
                                      
                                                     
 
                                    
                 
                              


                    





                                                        
         
     
                          

 
                              
 
                            
                                      
                                  
     
                                                             

               



                                                   
                                                     
                                                       
 
                                                                              

                                                                                      
                                                     


                                           
                                                                               
     

 
                              
 
                            
                                      
 
                       
     
                                                               

                                           
                                             
 
                                                                                         
         
                                                                                   


                                             
         
     








                                               
                                                                                         






                                                                
 
                     

 










                                                            

                                                    
                              

                                                                      

                          

                                               
                                                                                               
     
                                                                        




                                         
/*
 *  The Mana World Server
 *  Copyright 2004 The Mana World Development Team
 *
 *  This file is part of The Mana World.
 *
 *  The Mana World is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  The Mana World is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with The Mana World; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  $Id$
 */

#include <cassert>

#include "defines.h"
#include "point.h"
#include "game-server/accountconnection.hpp"
#include "game-server/gamehandler.hpp"
#include "game-server/item.hpp"
#include "game-server/map.hpp"
#include "game-server/mapcomposite.hpp"
#include "game-server/mapmanager.hpp"
#include "game-server/state.hpp"
#include "net/messageout.hpp"
#include "utils/logger.h"

void State::updateMap(MapComposite *map)
{
    // 1. update object status.
    std::vector< Thing * > const &things = map->getEverything();
    for (std::vector< Thing * >::const_iterator i = things.begin(),
         i_end = things.end(); i != i_end; ++i)
    {
        (*i)->update();
    }

    // 2. perform attacks.
    for (MovingObjectIterator i(map->getWholeMapIterator()); i; ++i)
    {
        MovingObject *o = *i;
        if (o->getUpdateFlags() & UPDATEFLAG_ATTACK)
        {
            static_cast< Being * >(o)->performAttack(map);
        }
    }

    // 3. move objects around and update zones.
    for (MovingObjectIterator i(map->getWholeMapIterator()); i; ++i)
    {
        (*i)->move();
    }

    // 4. remove dead beings.
    for (MovingObjectIterator i(map->getWholeMapIterator()); i; ++i)
    {
        if ((*i)->getUpdateFlags() & UPDATEFLAG_REMOVE)
        {
            DelayedEvent e = { EVENT_REMOVE};
            enqueueEvent((*i), e);
        }
    }

    // 5. update the map itself.
    map->update();
}

void State::informPlayer(MapComposite *map, Character *p)
{
    MessageOut moveMsg(GPMSG_BEINGS_MOVE);
    MessageOut damageMsg(GPMSG_BEINGS_DAMAGE);
    Point pold = p->getOldPosition(), ppos = p->getPosition();
    int pid = p->getPublicID(), pflags = p->getUpdateFlags();

    // Inform client about activities of other beings near its character
    for (MovingObjectIterator i(map->getAroundCharacterIterator(p, AROUND_AREA)); i; ++i)
    {
        MovingObject *o = *i;

        Point oold = o->getOldPosition(), opos = o->getPosition();
        int otype = o->getType();
        int oid = o->getPublicID(), oflags = o->getUpdateFlags();
        int flags = 0;
        bool willBeInRange = ppos.inRangeOf(opos, AROUND_AREA);

        if (willBeInRange)
        {
            // Send attack messages.
            if ((oflags & UPDATEFLAG_ATTACK) && oid != pid)
            {
                MessageOut AttackMsg(GPMSG_BEING_ATTACK);
                AttackMsg.writeShort(oid);
                gameHandler->sendTo(p, AttackMsg);
            }

            // Send state change messages.
            if ((oflags & UPDATEFLAG_ACTIONCHANGE))
            {
                MessageOut ActionMsg(GPMSG_BEING_ACTION_CHANGE);
                ActionMsg.writeShort(oid);
                ActionMsg.writeByte(static_cast< Being * >(o)->getAction());
                gameHandler->sendTo(p, ActionMsg);
            }

            // Send leave messages of dead beings
            if ((oflags & UPDATEFLAG_REMOVE))
            {
                MessageOut leaveMsg(GPMSG_BEING_LEAVE);
                leaveMsg.writeShort(oid);
                gameHandler->sendTo(p, leaveMsg);
            }

            // Send damage messages.
            if (o->canFight())
            {
                Being *victim = static_cast< Being * >(o);
                Hits const &hits = victim->getHitsTaken();
                for (Hits::const_iterator j = hits.begin(),
                     j_end = hits.end(); j != j_end; ++j)
                {
                    damageMsg.writeShort(oid);
                    damageMsg.writeShort(*j);
                }
            }
        }

        // Check if this character and this moving object were around.
        bool wereInRange = pold.inRangeOf(oold, AROUND_AREA) &&
                           !((pflags | oflags) & UPDATEFLAG_NEW_ON_MAP);

        // Send enter/leaver messages.
        if (!wereInRange)
        {
            // o was outside p's range.
            if (!willBeInRange)
            {
                // Nothing to report: o will not be inside p's range.
                continue;
            }
            flags |= MOVING_POSITION;

            MessageOut enterMsg(GPMSG_BEING_ENTER);
            enterMsg.writeByte(otype);
            enterMsg.writeShort(oid);
            enterMsg.writeByte(static_cast< Being *>(o)->getAction());
            enterMsg.writeShort(opos.x);
            enterMsg.writeShort(opos.y);
            switch (otype) {
                case OBJECT_CHARACTER:
                {
                    Character *q = static_cast< Character * >(o);
                    enterMsg.writeString(q->getName());
                    enterMsg.writeByte(q->getHairStyle());
                    enterMsg.writeByte(q->getHairColor());
                    enterMsg.writeByte(q->getGender());
                } break;
                case OBJECT_MONSTER:
                {
                    enterMsg.writeShort(0); // TODO: The monster ID
                } break;
                default:
                    assert(false); // TODO
            }
            gameHandler->sendTo(p, enterMsg);
            continue;
        }
        else if (!willBeInRange)
        {
            // o is no longer visible from p.
            MessageOut leaveMsg(GPMSG_BEING_LEAVE);
            leaveMsg.writeShort(oid);
            gameHandler->sendTo(p, leaveMsg);
            continue;
        }
        else if (oold == opos)
        {
            // o does not move, nothing to report.
            continue;
        }

        /* At this point, either o has entered p's range, either o is
           moving inside p's range. Report o's movements. */

        Point odst = o->getDestination();
        if (opos != odst)
        {
            flags |= MOVING_POSITION;
            if (oflags & UPDATEFLAG_NEW_DESTINATION)
            {
                flags |= MOVING_DESTINATION;
            }
        }
        else
        {
            // No need to synchronize on the very last step.
            flags |= MOVING_DESTINATION;
        }

        // Send move messages.
        moveMsg.writeShort(oid);
        moveMsg.writeByte(flags);
        if (flags & MOVING_POSITION)
        {
            moveMsg.writeCoordinates(opos.x / 32, opos.y / 32);
        }
        if (flags & MOVING_DESTINATION)
        {
            moveMsg.writeShort(odst.x);
            moveMsg.writeShort(odst.y);
        }
    }

    // Do not send a packet if nothing happened in p's range.
    if (moveMsg.getLength() > 2)
        gameHandler->sendTo(p, moveMsg);

    if (damageMsg.getLength() > 2)
        gameHandler->sendTo(p, damageMsg);

    // Inform client about attribute changes of its character
    MessageOut attributeUpdateMsg(GPMSG_PLAYER_ATTRIBUTE_UPDATE);
    p->writeAttributeUpdateMessage(attributeUpdateMsg);
    if (attributeUpdateMsg.getLength() > 2)
        gameHandler->sendTo(p, attributeUpdateMsg);

    // Inform client about items on the ground around its character
    MessageOut itemMsg(GPMSG_ITEMS);
    for (FixedObjectIterator i(map->getAroundCharacterIterator(p, AROUND_AREA)); i; ++i)
    {
        assert((*i)->getType() == OBJECT_ITEM);
        Item *o = static_cast< Item * >(*i);
        Point opos = o->getPosition();
        int oflags = o->getUpdateFlags();
        bool willBeInRange = ppos.inRangeOf(opos, AROUND_AREA);
        bool wereInRange = pold.inRangeOf(opos, AROUND_AREA) &&
                           !((pflags | oflags) & UPDATEFLAG_NEW_ON_MAP);

        if (willBeInRange ^ wereInRange)
        {
            if (oflags & UPDATEFLAG_NEW_ON_MAP)
            {
                MessageOut appearMsg(GPMSG_ITEM_APPEAR);
                appearMsg.writeShort(o->getItemClass()->getDatabaseID());
                appearMsg.writeShort(opos.x);
                appearMsg.writeShort(opos.y);
                gameHandler->sendTo(p, appearMsg);
            }
            else
            {
                itemMsg.writeShort(willBeInRange ? o->getItemClass()->getDatabaseID() : 0);
                itemMsg.writeShort(opos.x);
                itemMsg.writeShort(opos.y);
            }
        }
    }

    // Do not send a packet if nothing happened in p's range.
    if (itemMsg.getLength() > 2)
        gameHandler->sendTo(p, itemMsg);
}

#ifndef NDEBUG
static bool dbgLockObjects;
#endif

void State::update()
{
#   ifndef NDEBUG
    dbgLockObjects = true;
#   endif

    // Update game state (update AI, etc.)
    MapManager::Maps const &maps = mapManager->getMaps();
    for (MapManager::Maps::const_iterator m = maps.begin(), m_end = maps.end(); m != m_end; ++m)
    {
        MapComposite *map = m->second;
        if (!map->isActive())
        {
            continue;
        }

        updateMap(map);

        for (CharacterIterator p(map->getWholeMapIterator()); p; ++p)
        {
            informPlayer(map, *p);
        }

        for (ObjectIterator i(map->getWholeMapIterator()); i; ++i)
        {
            Object *o = *i;
            o->clearUpdateFlags();
            if (o->canFight())
            {
                static_cast< Being * >(o)->clearHitsTaken();
            }
        }
    }

#   ifndef NDEBUG
    dbgLockObjects = false;
#   endif

    // Take care of events that were delayed because of their side effects.
    for (DelayedEvents::iterator i = delayedEvents.begin(),
         i_end = delayedEvents.end(); i != i_end; ++i)
    {
        DelayedEvent const &e = i->second;
        Object *o = i->first;
        switch (e.type)
        {
            case EVENT_REMOVE:
            {
                remove(o);
                if (o->getType() == OBJECT_CHARACTER)
                {
                    gameHandler->kill(static_cast< Character * >(o));
                }
                delete o;
            } break;

            case EVENT_INSERT:
            {
                insert(o);
            } break;

            case EVENT_WARP:
            {
                remove(o);
                Point pos(e.x, e.y);
                o->setMap(e.map);
                o->setPosition(pos);

                assert(o->getType() == OBJECT_CHARACTER);
                Character *p = static_cast< Character * >(o);
                /* Force update of persistent data on map change, so that
                   characters can respawn at the start of the map after a death or
                   a disconnection. */
                accountHandler->sendCharacterData(p);

                if (e.map->getMap())
                {
                    insert(o);
                }
                else
                {
                    MessageOut msg(GAMSG_REDIRECT);
                    msg.writeLong(p->getDatabaseID());
                    accountHandler->send(msg);
                    gameHandler->prepareServerChange(p);
                }
            } break;
        }
    }
    delayedEvents.clear();
}

void State::insert(Thing *ptr)
{
    assert(!dbgLockObjects);
    MapComposite *map = ptr->getMap();
    if (!map || !map->insert(ptr))
    {
        // TODO: Deal with failure to place Thing on the map.
        return;
    }

    if (ptr->isVisible())
    {
        Object *obj = static_cast< Object * >(ptr);
        obj->raiseUpdateFlags(UPDATEFLAG_NEW_ON_MAP);
        if (obj->getType() != OBJECT_CHARACTER) return;

        /* Since the character doesn't know yet where on the world he is after
           connecting to the map server, we send him an initial change map message. */
        MessageOut mapChangeMessage(GPMSG_PLAYER_MAP_CHANGE);
        mapChangeMessage.writeString(map->getName());
        Point pos = obj->getPosition();
        mapChangeMessage.writeShort(pos.x);
        mapChangeMessage.writeShort(pos.y);
        gameHandler->sendTo(static_cast< Character * >(obj), mapChangeMessage);
    }
}

void State::remove(Thing *ptr)
{
    assert(!dbgLockObjects);
    MapComposite *map = ptr->getMap();

    if (ptr->canMove())
    {
        MovingObject *obj = static_cast< MovingObject * >(ptr);
        MessageOut msg(GPMSG_BEING_LEAVE);
        msg.writeShort(obj->getPublicID());
        Point objectPos = obj->getPosition();

        for (CharacterIterator p(map->getAroundObjectIterator(obj, AROUND_AREA)); p; ++p)
        {
            if (*p != obj && objectPos.inRangeOf((*p)->getPosition(), AROUND_AREA))
            {
                gameHandler->sendTo(*p, msg);
            }
        }
    }
    else if (ptr->getType() == OBJECT_ITEM)
    {
        Item *obj = static_cast< Item * >(ptr);
        Point pos = obj->getPosition();
        MessageOut msg(GPMSG_ITEMS);
        msg.writeShort(0);
        msg.writeShort(pos.x);
        msg.writeShort(pos.y);

        for (CharacterIterator p(map->getAroundObjectIterator(obj, AROUND_AREA)); p; ++p)
        {
            if (pos.inRangeOf((*p)->getPosition(), AROUND_AREA))
            {
                gameHandler->sendTo(*p, msg);
            }
        }
    }

    map->remove(ptr);
}

void State::enqueueEvent(Object *ptr, DelayedEvent const &e)
{
    std::pair< DelayedEvents::iterator, bool > p =
        delayedEvents.insert(std::make_pair(ptr, e));
    // Delete events take precedence over other events.
    if (!p.second && e.type == EVENT_REMOVE)
    {
        p.first->second.type = EVENT_REMOVE;
    }
}

void State::sayAround(Object *obj, std::string text)
{
    MessageOut msg(GPMSG_SAY);
    msg.writeShort(!obj->canMove() ? 65535 :
                   static_cast< MovingObject * >(obj)->getPublicID());
    msg.writeString(text);

    Point speakerPosition = obj->getPosition();

    for (CharacterIterator i(obj->getMap()->getAroundObjectIterator(obj, AROUND_AREA)); i; ++i)
    {
        if (speakerPosition.inRangeOf((*i)->getPosition(), AROUND_AREA))
        {
            gameHandler->sendTo(*i, msg);
        }
    }
}