summaryrefslogblamecommitdiff
path: root/src/game-server/gamehandler.cpp
blob: 34988edc6bbb17cc08d0ccd3d0a5907c33dcfe53 (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 "game-server/gamehandler.hpp"

#include <cassert>
#include <map>

#include "game-server/accountconnection.hpp"
#include "game-server/inventory.hpp"
#include "game-server/item.hpp"
#include "game-server/itemmanager.hpp"
#include "game-server/map.hpp"
#include "game-server/mapcomposite.hpp"
#include "game-server/npc.hpp"
#include "game-server/state.hpp"
#include "game-server/trade.hpp"
#include "net/messagein.hpp"
#include "net/messageout.hpp"
#include "net/netcomputer.hpp"
#include "utils/logger.h"
#include "utils/tokendispenser.hpp"

GameHandler::GameHandler():
    mTokenCollector(this)
{
}

bool GameHandler::startListen(enet_uint16 port)
{
    LOG_INFO("Game handler started:");
    return ConnectionHandler::startListen(port);
}

NetComputer *GameHandler::computerConnected(ENetPeer *peer)
{
    return new GameClient(peer);
}

void GameHandler::computerDisconnected(NetComputer *comp)
{
    GameClient &computer = *static_cast< GameClient * >(comp);

    if (computer.status == CLIENT_QUEUED)
    {
        mTokenCollector.deletePendingClient(&computer);
    }
    else if (Character *ch = computer.character)
    {
        accountHandler->sendCharacterData(ch);
        GameState::remove(ch);
        delete ch;
    }
    delete &computer;
}

void GameHandler::kill(Character *ch)
{
    GameClient *client = ch->getClient();
    assert(client != NULL);
    client->character = NULL;
    client->status = CLIENT_LOGIN;
}

void GameHandler::prepareServerChange(Character *ch)
{
    GameClient *client = ch->getClient();
    assert(client != NULL);
    client->status = CLIENT_CHANGE_SERVER;
}

void GameHandler::completeServerChange(int id, std::string const &token,
                                       std::string const &address, int port)
{
    for (NetComputers::const_iterator i = clients.begin(),
         i_end = clients.end(); i != i_end; ++i)
    {
        GameClient *c = static_cast< GameClient * >(*i);
        if (c->status == CLIENT_CHANGE_SERVER &&
            c->character->getDatabaseID() == id)
        {
            MessageOut msg(GPMSG_PLAYER_SERVER_CHANGE);
            msg.writeString(token, MAGIC_TOKEN_LENGTH);
            msg.writeString(address);
            msg.writeShort(port);
            c->send(msg);
            delete c->character;
            c->character = NULL;
            c->status = CLIENT_LOGIN;
            return;
        }
    }
}

void GameHandler::process()
{
    ConnectionHandler::process();
}

static MovingObject *findBeingNear(Object *p, int id)
{
    MapComposite *map = p->getMap();
    Point const &ppos = p->getPosition();
    // TODO: use a less arbitrary value.
    for (MovingObjectIterator i(map->getAroundPointIterator(ppos, 48)); i; ++i)
    {
        MovingObject *o = *i;
        if (o->getPublicID() != id) continue;
        return ppos.inRangeOf(o->getPosition(), 48) ? o : NULL;
    }
    return NULL;
}

static Character *findCharacterNear(Object *p, int id)
{
    MapComposite *map = p->getMap();
    Point const &ppos = p->getPosition();
    // TODO: use a less arbitrary value.
    for (CharacterIterator i(map->getAroundPointIterator(ppos, 48)); i; ++i)
    {
        Character *o = *i;
        if (o->getPublicID() != id) continue;
        return ppos.inRangeOf(o->getPosition(), 48) ? o : NULL;
    }
    return NULL;
}

void GameHandler::processMessage(NetComputer *comp, MessageIn &message)
{
    GameClient &computer = *static_cast< GameClient * >(comp);
    MessageOut result;

    if (computer.status == CLIENT_LOGIN)
    {
        if (message.getId() != PGMSG_CONNECT) return;

        std::string magic_token = message.readString(MAGIC_TOKEN_LENGTH);
        computer.status = CLIENT_QUEUED; // Before the addPendingClient
        mTokenCollector.addPendingClient(magic_token, &computer);
        return;
    }
    else if (computer.status != CLIENT_CONNECTED)
    {
        return;
    }

    switch (message.getId())
    {
        case PGMSG_SAY:
        {
            std::string say = message.readString();
            GameState::sayAround(computer.character, say);
        } break;

        case PGMSG_NPC_TALK:
        case PGMSG_NPC_TALK_NEXT:
        case PGMSG_NPC_SELECT:
        {
            int id = message.readShort();
            MovingObject *o = findBeingNear(computer.character, id);
            if (!o || o->getType() != OBJECT_NPC) break;

            NPC *q = static_cast< NPC * >(o);
            if (message.getId() == PGMSG_NPC_SELECT)
            {
                q->select(computer.character, message.readByte());
            }
            else
            {
                q->prompt(computer.character, message.getId() == PGMSG_NPC_TALK);
            }
        } break;

        case PGMSG_PICKUP:
        {
            int x = message.readShort();
            int y = message.readShort();
            Point ppos = computer.character->getPosition();

            // TODO: use a less arbitrary value.
            if (std::abs(x - ppos.x) + std::abs(y - ppos.y) < 48)
            {
                MapComposite *map = computer.character->getMap();
                Point ipos(x, y);
                for (FixedObjectIterator i(map->getAroundPointIterator(ipos, 0)); i; ++i)
                {
                    Object *o = *i;
                    Point opos = o->getPosition();
                    if (o->getType() == OBJECT_ITEM && opos.x == x && opos.y == y)
                    {
                        Item *item = static_cast< Item * >(o);
                        ItemClass *ic = item->getItemClass();
                        Inventory(computer.character)
                            .insert(ic->getDatabaseID(), item->getAmount());
                        GameState::remove(item);
                        break;
                    }
                }
            }
        } break;

        case PGMSG_DROP:
        {
            int slot = message.readByte();
            int amount = message.readByte();
            Inventory inv(computer.character);
            if (ItemClass *ic = ItemManager::getItem(inv.getItem(slot)))
            {
                int nb = inv.removeFromSlot(slot, amount);
                Item *item = new Item(ic, amount - nb);
                item->setMap(computer.character->getMap());
                item->setPosition(computer.character->getPosition());
                GameState::insert(item);
            }
        } break;

        case PGMSG_WALK:
        {
            int x = message.readShort();
            int y = message.readShort();
            Point dst(x, y);
            computer.character->setDestination(dst);

            // no response should be required
        } break;

        case PGMSG_EQUIP:
        {
            int slot = message.readByte();
            Inventory(computer.character).equip(slot);
        } break;

        case PGMSG_ATTACK:
        {
            LOG_DEBUG("Character " << computer.character->getPublicID()
                      << " attacks");
            computer.character->setDirection(message.readByte());
            computer.character->setAction(Being::ATTACK);
        } break;

        case PGMSG_ACTION_CHANGE:
        {
            Being::Action action = (Being::Action)message.readByte();
            Being::Action current = (Being::Action)computer.character->getAction();

            switch (action)
            {
                case Being::STAND:
                {
                    if (current == Being::SIT)
                        computer.character->setAction(Being::STAND);
                } break;
                case Being::SIT:
                {
                    if (current == Being::STAND)
                        computer.character->setAction(Being::SIT);
                } break;
                default:
                    break;
            }

        } break;

        case PGMSG_DISCONNECT:
        {
            bool reconnectAccount = (bool) message.readByte();

            result.writeShort(GPMSG_DISCONNECT_RESPONSE);
            result.writeByte(ERRMSG_OK); // It is, when control reaches here

            if (reconnectAccount)
            {
                std::string magic_token(utils::getMagicToken());
                result.writeString(magic_token, MAGIC_TOKEN_LENGTH);
                // No accountserver data, the client should remember that
                accountHandler->playerReconnectAccount(
                                   computer.character->getDatabaseID(),
                                   magic_token);
            }
            // TODO: implement a delayed remove
            GameState::remove(computer.character);

            accountHandler->sendCharacterData(computer.character);

            // Done with the character
            delete computer.character;
            computer.character = NULL;
            computer.status = CLIENT_LOGIN;
        } break;

        case PGMSG_TRADE_REQUEST:
        {
            int id = message.readShort();

            if (Trade *t = computer.character->getTrading())
            {
                if (t->request(computer.character, id)) break;
            }

            Character *q = findCharacterNear(computer.character, id);
            if (!q || q->getTrading())
            {
                result.writeShort(GPMSG_TRADE_CANCEL);
                break;
            }

            new Trade(computer.character, q);
        } break;

        case PGMSG_TRADE_CANCEL:
        case PGMSG_TRADE_ACCEPT:
        case PGMSG_TRADE_ADD_ITEM:
        {
            Trade *t = computer.character->getTrading();
            if (!t) break;

            switch (message.getId())
            {
                case PGMSG_TRADE_CANCEL:
                    t->cancel(computer.character);
                    break;
                case PGMSG_TRADE_ACCEPT :
                    t->accept(computer.character);
                    break;
                case PGMSG_TRADE_ADD_ITEM:
                    int slot = message.readByte();
                    t->addItem(computer.character, slot, message.readByte());
                    break;
            }
        } break;



// The following messages should be handled by the chat server, not the game server.
#if 0

        case PGMSG_GUILD_CREATE:
        {
            std::string name = message.readString();
            int characterId = computer.character->getDatabaseID();
            messageMap[characterId] = computer.character;
            accountHandler->playerCreateGuild(characterId, name);
        } break;
            
        case PGMSG_GUILD_INVITE:
        {
            short guildId = message.readShort();
            std::string member = message.readString();
            int characterId = computer.character->getDatabaseID();
            messageMap[characterId] = computer.character;
            accountHandler->playerInviteToGuild(characterId, guildId, member);
        } break;
            
        case PGMSG_GUILD_ACCEPT:
        {
            std::string guildName = message.readString();
            int characterId = computer.character->getDatabaseID();
            messageMap[characterId] = computer.character;
            accountHandler->playerAcceptInvite(characterId, guildName);
        } break;
            
        case PGMSG_GUILD_GET_MEMBERS:
        {
            short guildId = message.readShort();
            int characterId = computer.character->getDatabaseID();
            messageMap[characterId] = computer.character;
            accountHandler->getGuildMembers(characterId, guildId);
        } break;
            
        case PGMSG_GUILD_QUIT:
        {
            short guildId = message.readShort();
            int characterId = computer.character->getDatabaseID();
            messageMap[characterId] = computer.character;
            accountHandler->quitGuild(characterId, guildId);
        } break;
#endif

        default:
            LOG_WARN("Invalid message type");
            result.writeShort(XXMSG_INVALID);
            break;
    }

    if (result.getLength() > 0)
        computer.send(result);
}

void GameHandler::sendTo(Character *beingPtr, MessageOut &msg)
{
    GameClient *client = beingPtr->getClient();
    assert(client && client->status == CLIENT_CONNECTED);
    client->send(msg);
}

void
GameHandler::tokenMatched(GameClient* computer, Character* character)
{
    computer->character = character;
    computer->status = CLIENT_CONNECTED;

    character->setClient(computer);

    MessageOut result(GPMSG_CONNECT_RESPONSE);
    result.writeByte(ERRMSG_OK);
    computer->send(result);

    GameState::insert(character);

    Inventory(character).sendFull();
}

void
GameHandler::deletePendingClient(GameClient* computer)
{
    // Something might have changed since it was inserted
    if (computer->status != CLIENT_QUEUED) return;

    MessageOut msg(GPMSG_CONNECTION_TIMEDOUT);

    // The computer will be deleted when the disconnect event is processed
    computer->disconnect(msg);
}

void
GameHandler::deletePendingConnect(Character* character)
{
    delete character;
}