/* * 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 #include "controller.h" #include "defines.h" #include "map.h" #include "point.h" #include "game-server/gamehandler.hpp" #include "game-server/mapcomposite.hpp" #include "game-server/mapmanager.hpp" #include "game-server/state.hpp" #include "net/messageout.hpp" #include "utils/logger.h" State::State() { // Create 10 maggots for testing purposes for (int i = 0; i < 10; i++) { Being *being = new Controlled(OBJECT_MONSTER); being->setSpeed(150); being->setMapId(1); Point pos = { 720, 900 }; being->setPosition(pos); addObject(ObjectPtr(being)); } } State::~State() { for (std::map< unsigned, MapComposite * >::iterator i = maps.begin(), i_end = maps.end(); i != i_end; ++i) { delete i->second; } } void State::updateMap(MapComposite *map) { // 1. update object status. for (ObjectIterator i(map->getWholeMapIterator()); i; ++i) { (*i)->update(); } // 2. perform attacks. for (MovingObjectIterator i(map->getWholeMapIterator()); i; ++i) { MovingObject *o = *i; if (o->getUpdateFlags() & ATTACK) { static_cast< Being * >(o)->performAttack(map); } } // 3. move objects around and update zones. for (MovingObjectIterator i(map->getWholeMapIterator()); i; ++i) { (*i)->move(); } map->update(); } void State::informPlayer(MapComposite *map, Player *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(); for (MovingObjectIterator i(map->getAroundPlayerIterator(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; // Send attack messages. if ((oflags & ATTACK) && oid != pid && ppos.inRangeOf(opos)) { MessageOut AttackMsg(GPMSG_BEING_ATTACK); AttackMsg.writeShort(oid); gameHandler->sendTo(p, AttackMsg); } // Send damage messages. if (otype == OBJECT_PLAYER || otype == OBJECT_MONSTER) { 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 whether this player and this moving object were around the last time and whether they will be around the next time. */ bool wereInRange = pold.inRangeOf(oold) && !((pflags | oflags) & NEW_ON_MAP); bool willBeInRange = ppos.inRangeOf(opos); // 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_DESTINATION; MessageOut enterMsg(GPMSG_BEING_ENTER); enterMsg.writeByte(otype); enterMsg.writeShort(oid); switch (otype) { case OBJECT_PLAYER: { Player *q = static_cast< Player * >(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); } 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.x == opos.x && oold.y == opos.y) { // 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.x != odst.x || opos.y != odst.y) { flags |= MOVING_POSITION; if (oflags & 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); } void State::update() { /* * Update game state (update AI, etc.) */ for (std::map< unsigned, MapComposite * >::iterator m = maps.begin(), m_end = maps.end(); m != m_end; ++m) { MapComposite *map = m->second; updateMap(map); /* * Inform clients about changes in the game state */ for (PlayerIterator p(map->getWholeMapIterator()); p; ++p) { informPlayer(map, *p); } for (ObjectIterator i(map->getWholeMapIterator()); i; ++i) { Object *o = *i; o->clearUpdateFlags(); int type = o->getType(); if (type == OBJECT_PLAYER || type == OBJECT_MONSTER) { static_cast< Being * >(o)->clearHitsTaken(); } } } } void State::addObject(ObjectPtr objectPtr) { unsigned mapId = objectPtr->getMapId(); MapComposite *map = loadMap(mapId); if (!map || !map->insert(objectPtr)) { // TODO: Deal with failure to place Object on the map. return; } objectPtr->raiseUpdateFlags(NEW_ON_MAP); if (objectPtr->getType() != OBJECT_PLAYER) return; Player *playerPtr = static_cast< Player * >(objectPtr.get()); /* Since the player 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(mapManager->getMapName(mapId)); Point pos = playerPtr->getPosition(); mapChangeMessage.writeShort(pos.x); mapChangeMessage.writeShort(pos.y); mapChangeMessage.writeByte(0); gameHandler->sendTo(playerPtr, mapChangeMessage); } void State::removeObject(ObjectPtr objectPtr) { unsigned mapId = objectPtr->getMapId(); std::map< unsigned, MapComposite * >::iterator m = maps.find(mapId); if (m == maps.end()) return; MapComposite *map = m->second; int type = objectPtr->getType(); if (type == OBJECT_MONSTER || type == OBJECT_PLAYER || type == OBJECT_NPC) { MovingObject *obj = static_cast< MovingObject * >(objectPtr.get()); MessageOut msg(GPMSG_BEING_LEAVE); msg.writeShort(obj->getPublicID()); Point objectPos = obj->getPosition(); for (PlayerIterator p(map->getAroundObjectIterator(obj, AROUND_AREA)); p; ++p) { if (*p != obj && objectPos.inRangeOf((*p)->getPosition())) { gameHandler->sendTo(*p, msg); } } } map->remove(objectPtr); } MapComposite *State::loadMap(unsigned mapId) { std::map< unsigned, MapComposite * >::iterator m = maps.find(mapId); if (m != maps.end()) return m->second; Map *map = mapManager->getMap(mapId); assert(map); MapComposite *tmp = new MapComposite(map); maps[mapId] = tmp; // will need to load extra map related resources here also return tmp; } void State::sayAround(Object *obj, std::string text) { unsigned short id = 65535; int type = obj->getType(); if (type == OBJECT_PLAYER || type == OBJECT_NPC || type == OBJECT_MONSTER) { id = static_cast< MovingObject * >(obj)->getPublicID(); } MessageOut msg(GPMSG_SAY); msg.writeShort(id); msg.writeString(text); std::map< unsigned, MapComposite * >::iterator m = maps.find(obj->getMapId()); if (m == maps.end()) return; MapComposite *map = m->second; Point speakerPosition = obj->getPosition(); for (PlayerIterator i(map->getAroundObjectIterator(obj, AROUND_AREA)); i; ++i) { if (speakerPosition.inRangeOf((*i)->getPosition())) { gameHandler->sendTo(*i, msg); } } }