bool MapManager::raiseActive(int mapId) { Maps::iterator i = maps.find(mapId); assert(i != maps.end()); MapComposite *composite = i->second; if (composite->isActive()) { return true; } std::string file = "maps/" + composite->getName() + ".tmx"; if (!ResourceManager::exists(file)) { file += ".gz"; } if (MapReader::readMap(file, composite)) { LOG_INFO("Activated map \"" << file << "\" (id " << mapId << ")"); return true; } else { LOG_WARN("Couldn't activate invalid map \"" << file << "\" (id " << mapId << ")"); return false; } }
void GameHandler::handlePickup(GameClient &client, MessageIn &message) { const int x = message.readInt16(); const int y = message.readInt16(); const Point ppos = client.character->getComponent<ActorComponent>()->getPosition(); // TODO: use a less arbitrary value. if (std::abs(x - ppos.x) + std::abs(y - ppos.y) < 48) { MapComposite *map = client.character->getMap(); Point ipos(x, y); for (FixedActorIterator i(map->getAroundPointIterator(ipos, 0)); i; ++i) { Entity *o = *i; Point opos = o->getComponent<ActorComponent>()->getPosition(); if (o->getType() == OBJECT_ITEM && opos.x == x && opos.y == y) { ItemComponent *item = o->getComponent<ItemComponent>(); ItemClass *ic = item->getItemClass(); int amount = item->getAmount(); if (!Inventory(client.character).insert(ic->getDatabaseID(), amount)) { GameState::remove(o); // We only do this when items are to be kept in memory // between two server restart. if (!Configuration::getValue("game_floorItemDecayTime", 0)) { // Remove the floor item from map accountHandler->removeFloorItems(map->getID(), ic->getDatabaseID(), amount, x, y); } // log transaction std::stringstream str; str << "User picked up item " << ic->getDatabaseID() << " at " << opos.x << "x" << opos.y; auto *characterComponent = client.character ->getComponent<CharacterComponent>(); accountHandler->sendTransaction( characterComponent->getDatabaseID(), TRANS_ITEM_PICKUP, str.str() ); } break; } } } }
static Being *findBeingNear(Actor *p, int id) { MapComposite *map = p->getMap(); const Point &ppos = p->getPosition(); // See map.h for tiles constants const int pixelDist = DEFAULT_TILE_LENGTH * TILES_TO_BE_NEAR; for (BeingIterator i(map->getAroundPointIterator(ppos, pixelDist)); i; ++i) { Being *b = *i; if (b->getPublicID() != id) continue; return ppos.inRangeOf(b->getPosition(), pixelDist) ? b : 0; } return 0; }
static Actor *findActorNear(Actor *p, int id) { MapComposite *map = p->getMap(); const Point &ppos = p->getPosition(); // See map.hpp for tiles constants const int pixelDist = DEFAULT_TILE_WIDTH * TILES_TO_BE_NEAR; for (ActorIterator i(map->getAroundPointIterator(ppos, pixelDist)); i; ++i) { Actor *a = *i; if (a->getPublicID() != id) continue; return ppos.inRangeOf(a->getPosition(), pixelDist) ? a : 0; } return 0; }
void AccountConnection::sendStatistics() { MessageOut msg(GAMSG_STATISTICS); const MapManager::Maps &maps = MapManager::getMaps(); for (MapManager::Maps::const_iterator i = maps.begin(), i_end = maps.end(); i != i_end; ++i) { MapComposite *m = i->second; if (!m->isActive()) continue; msg.writeInt16(i->first); int nbEntities = 0, nbMonsters = 0; typedef std::vector< Entity * > Entities; const Entities &things = m->getEverything(); std::vector< int > players; for (Entities::const_iterator j = things.begin(), j_end = things.end(); j != j_end; ++j) { Entity *t = *j; switch (t->getType()) { case OBJECT_CHARACTER: players.push_back (static_cast< Character * >(t)->getDatabaseID()); break; case OBJECT_MONSTER: ++nbMonsters; break; default: ++nbEntities; } } msg.writeInt16(nbEntities); msg.writeInt16(nbMonsters); msg.writeInt16(players.size()); for (std::vector< int >::const_iterator j = players.begin(), j_end = players.end(); j != j_end; ++j) { msg.writeInt32(*j); } } send(msg); }
static Entity *findCharacterNear(Entity *p, int id) { MapComposite *map = p->getMap(); const Point &ppos = p->getComponent<ActorComponent>()->getPosition(); // See map.h for tiles constants const int pixelDist = DEFAULT_TILE_LENGTH * TILES_TO_BE_NEAR; for (CharacterIterator i(map->getAroundPointIterator(ppos, pixelDist)); i; ++i) { Entity *c = *i; if (c->getComponent<ActorComponent>()->getPublicID() != id) continue; if (ppos.inRangeOf(c->getComponent<ActorComponent>()->getPosition(), pixelDist)) return c; return 0; } return 0; }
void GameHandler::handlePartyInvite(GameClient &client, MessageIn &message) { MapComposite *map = client.character->getMap(); const int visualRange = Configuration::getValue("game_visualRange", 448); std::string invitee = message.readString(); if (invitee == client.character->getComponent<BeingComponent>()->getName()) return; for (CharacterIterator it(map->getWholeMapIterator()); it; ++it) { if ((*it)->getComponent<BeingComponent>()->getName() == invitee) { // calculate if the invitee is within the visual range auto *inviterComponent = client.character->getComponent<ActorComponent>(); auto *inviteeComponent = (*it)->getComponent<ActorComponent>(); const Point &inviterPosition = inviterComponent->getPosition(); const Point &inviteePosition = inviteeComponent->getPosition(); const int dx = std::abs(inviterPosition.x - inviteePosition.x); const int dy = std::abs(inviterPosition.y - inviteePosition.y); if (visualRange > std::max(dx, dy)) { MessageOut out(GCMSG_PARTY_INVITE); out.writeString(client.character ->getComponent<BeingComponent>()->getName()); out.writeString(invitee); accountHandler->send(out); return; } break; } } // Invitee was not found or is too far away MessageOut out(GPMSG_PARTY_INVITE_ERROR); out.writeString(invitee); client.send(out); }
/** * Initializes the map content. This creates the warps, spawn areas, npcs and * other scripts. */ void MapComposite::initializeContent() { mContent = new MapContent(mMap); const std::vector<MapObject *> &objects = mMap->getObjects(); for (size_t i = 0; i < objects.size(); ++i) { const MapObject *object = objects.at(i); const std::string &type = object->getType(); if (utils::compareStrI(type, "WARP") == 0) { const std::string destMapName = object->getProperty("DEST_MAP"); const Rectangle &sourceBounds = object->getBounds(); MapComposite *destMap = MapManager::getMap(destMapName); // check destination map if (!destMap) { if (destMapName.empty()) { // this must be a one way warp target continue; } LOG_ERROR("Warp \"" << object->getName() << "\" targets missing map \"" << destMapName << "\" in " << getName()); continue; } TriggerAction* action; if (object->hasProperty("DEST_NAME")) { // warp to an object // get destination object name const std::string destMapObjectName = object->getProperty("DEST_NAME"); // get target object and validate it const MapObject *destination = destMap->findMapObject(destMapObjectName, "WARP"); if (!destination) { LOG_ERROR("Warp \"" << object->getName() << "\" from map " << getName() << " targets missing warp \"" << destMapObjectName << "\" " << " on map " << destMap->getName()); continue; } const Rectangle &destinationBounds = destination->getBounds(); const std::string &exit = destination->getProperty("EXIT_DIRECTION"); if (exit.empty()) { // old style WARP, warp to center of that object int destX = destinationBounds.x + destinationBounds.w / 2; int destY = destinationBounds.y + destinationBounds.h / 2; action = new WarpAction(destMap, Point(destX, destY)); } else { // newer and cooler warp AutowarpAction::ExitDirection exitDir; // find the exit direction if (utils::compareStrI(exit, "NORTH") == 0) { exitDir = AutowarpAction::ExitNorth; } else if (utils::compareStrI(exit, "EAST") == 0) { exitDir = AutowarpAction::ExitEast; } else if (utils::compareStrI(exit, "SOUTH") == 0) { exitDir = AutowarpAction::ExitSouth; } else if (utils::compareStrI(exit, "WEST") == 0) { exitDir = AutowarpAction::ExitWest; } else { // invalid or missing exit direction if (exit.empty()) { LOG_ERROR("Warp target \"" << destMapObjectName << "\" on map " << destMap->getName() << " is missing exit direction!"); } else { LOG_ERROR("Warp target \"" << destMapObjectName << "\" on map " << destMap->getName() << " has an invalid exit direction \"" << exit << "\"!"); } continue; } action = new AutowarpAction(destMap, sourceBounds, destinationBounds, exitDir); } } else if (object->hasProperty("DEST_X") && object->hasProperty("DEST_Y")) { // warp to absolute position int destX = utils::stringToInt(object->getProperty("DEST_X")); int destY = utils::stringToInt(object->getProperty("DEST_Y")); action = new WarpAction(destMap, Point(destX, destY)); } else { LOG_ERROR("Warp \"" << object->getName() << "\" on map " << getName() << " is invalid!"); continue; } // add this trigger to the map Entity *entity = new Entity(OBJECT_OTHER, this); entity->addComponent( new TriggerAreaComponent( sourceBounds, action, false ) ); insert(entity); } else if (utils::compareStrI(type, "SPAWN") == 0) { MonsterClass *monster = 0; int maxBeings = utils::stringToInt(object->getProperty("MAX_BEINGS")); int spawnRate = utils::stringToInt(object->getProperty("SPAWN_RATE")); std::string monsterName = object->getProperty("MONSTER_ID"); int monsterId = utils::stringToInt(monsterName); if (monsterId) { monster = monsterManager->getMonster(monsterId); if (!monster) { LOG_WARN("Couldn't find monster ID " << monsterId << " for spawn area"); } } else { monster = monsterManager->getMonsterByName(monsterName); if (!monster) { LOG_WARN("Couldn't find monster " << monsterName << " for spawn area"); } } if (monster && maxBeings && spawnRate) { Entity *entity = new Entity(OBJECT_OTHER, this); SpawnAreaComponent *spawnArea = new SpawnAreaComponent(monster, object->getBounds(), maxBeings, spawnRate); entity->addComponent(spawnArea); insert(entity); } } else if (utils::compareStrI(type, "NPC") == 0) { int npcId = utils::stringToInt(object->getProperty("NPC_ID")); std::string gender = object->getProperty("GENDER"); std::string scriptText = object->getProperty("SCRIPT"); if (npcId && !scriptText.empty()) { Script *script = ScriptManager::currentState(); script->loadNPC(object->getName(), npcId, ManaServ::getGender(gender), object->getX(), object->getY(), scriptText.c_str(), this); } else { LOG_WARN("Unrecognized format for npc"); } } else if (utils::compareStrI(type, "SCRIPT") == 0) { std::string scriptFilename = object->getProperty("FILENAME"); std::string scriptText = object->getProperty("TEXT"); Script *script = ScriptManager::currentState(); Script::Context context; context.map = this; if (!scriptFilename.empty()) { script->loadFile(scriptFilename, context); } else if (!scriptText.empty()) { std::string name = "'" + object->getName() + "'' in " + mName; script->load(scriptText.c_str(), name.c_str(), context); } else { LOG_WARN("Unrecognized format for script"); } } } }
void GameState::remove(Entity *ptr) { assert(!dbgLockObjects); MapComposite *map = ptr->getMap(); int visualRange = Configuration::getValue("game_visualRange", 448); ptr->signal_removed.emit(ptr); // DEBUG INFO switch (ptr->getType()) { case OBJECT_ITEM: LOG_DEBUG("Item removed: " << ptr->getComponent<ItemComponent>()->getItemClass()->getDatabaseID()); break; case OBJECT_NPC: LOG_DEBUG("NPC removed: " << ptr->getComponent<NpcComponent>()->getNpcId()); break; case OBJECT_CHARACTER: LOG_DEBUG("Player removed: " << static_cast<Being*>(ptr)->getName()); break; case OBJECT_EFFECT: LOG_DEBUG("Effect removed: " << ptr->getComponent<EffectComponent>()->getEffectId()); break; case OBJECT_MONSTER: LOG_DEBUG("Monster removed: " << static_cast<Monster*>(ptr)->getSpecy()->getId()); break; case OBJECT_ACTOR: case OBJECT_OTHER: default: LOG_DEBUG("Entity removed: " << ptr->getType()); break; } if (ptr->canMove()) { if (ptr->getType() == OBJECT_CHARACTER) { static_cast< Character * >(ptr)->cancelTransaction(); // remove characters online status accountHandler->updateOnlineStatus( static_cast< Character * >(ptr)->getDatabaseID(), false); } Actor *obj = static_cast< Actor * >(ptr); MessageOut msg(GPMSG_BEING_LEAVE); msg.writeInt16(obj->getPublicID()); Point objectPos = obj->getPosition(); for (CharacterIterator p(map->getAroundActorIterator(obj, visualRange)); p; ++p) { if (*p != obj && objectPos.inRangeOf((*p)->getPosition(), visualRange)) { gameHandler->sendTo(*p, msg); } } } else if (ptr->getType() == OBJECT_ITEM) { Actor *actor = static_cast<Actor*>(ptr); Point pos = actor->getPosition(); MessageOut msg(GPMSG_ITEMS); msg.writeInt16(0); msg.writeInt16(pos.x); msg.writeInt16(pos.y); for (CharacterIterator p(map->getAroundActorIterator(actor, visualRange)); p; ++p) { if (pos.inRangeOf((*p)->getPosition(), visualRange)) { gameHandler->sendTo(*p, msg); } } } map->remove(ptr); }
bool GameState::insert(Entity *ptr) { assert(!dbgLockObjects); MapComposite *map = ptr->getMap(); assert(map && map->isActive()); /* Non-visible objects have neither position nor public ID, so their insertion cannot fail. Take care of them first. */ if (!ptr->isVisible()) { map->insert(ptr); ptr->signal_inserted.emit(ptr); return true; } // Check that coordinates are actually valid. Actor *obj = static_cast< Actor * >(ptr); Map *mp = map->getMap(); Point pos = obj->getPosition(); if ((int)pos.x / mp->getTileWidth() >= mp->getWidth() || (int)pos.y / mp->getTileHeight() >= mp->getHeight()) { LOG_ERROR("Tried to insert an actor at position " << pos.x << ',' << pos.y << " outside map " << map->getID() << '.'); // Set an arbitrary small position. pos = Point(100, 100); obj->setPosition(pos); } if (!map->insert(obj)) { // The map is overloaded, no room to add a new actor LOG_ERROR("Too many actors on map " << map->getID() << '.'); return false; } obj->signal_inserted.emit(obj); // DEBUG INFO switch (obj->getType()) { case OBJECT_ITEM: LOG_DEBUG("Item inserted: " << obj->getComponent<ItemComponent>()->getItemClass()->getDatabaseID()); break; case OBJECT_NPC: LOG_DEBUG("NPC inserted: " << obj->getComponent<NpcComponent>()->getNpcId()); break; case OBJECT_CHARACTER: LOG_DEBUG("Player inserted: " << static_cast<Being*>(obj)->getName()); break; case OBJECT_EFFECT: LOG_DEBUG("Effect inserted: " << obj->getComponent<EffectComponent>()->getEffectId()); break; case OBJECT_MONSTER: LOG_DEBUG("Monster inserted: " << static_cast<Monster*>(obj)->getSpecy()->getId()); break; case OBJECT_ACTOR: case OBJECT_OTHER: default: LOG_DEBUG("Entity inserted: " << obj->getType()); break; } obj->raiseUpdateFlags(UPDATEFLAG_NEW_ON_MAP); if (obj->getType() != OBJECT_CHARACTER) return true; /* Since the player does not know yet where in the world its character is, we send a map-change message, even if it is the first time it connects to this server. */ MessageOut mapChangeMessage(GPMSG_PLAYER_MAP_CHANGE); mapChangeMessage.writeString(map->getName()); mapChangeMessage.writeInt16(pos.x); mapChangeMessage.writeInt16(pos.y); gameHandler->sendTo(static_cast< Character * >(obj), mapChangeMessage); // update the online state of the character accountHandler->updateOnlineStatus( static_cast< Character * >(obj)->getDatabaseID(), true); return true; }
void GameState::update(int tick) { currentTick = tick; #ifndef NDEBUG dbgLockObjects = true; #endif ScriptManager::currentState()->update(); // Update game state (update AI, etc.) const MapManager::Maps &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; map->update(); for (CharacterIterator p(map->getWholeMapIterator()); p; ++p) { informPlayer(map, *p); } for (ActorIterator it(map->getWholeMapIterator()); it; ++it) { Actor *a = *it; a->clearUpdateFlags(); if (a->canFight()) { static_cast< Being * >(a)->clearHitsTaken(); } } } # ifndef NDEBUG dbgLockObjects = false; # endif // Take care of events that were delayed because of their side effects. for (DelayedEvents::iterator it = delayedEvents.begin(), it_end = delayedEvents.end(); it != it_end; ++it) { const DelayedEvent &e = it->second; Actor *o = it->first; switch (e.type) { case EVENT_REMOVE: remove(o); if (o->getType() == OBJECT_CHARACTER) { Character *ch = static_cast< Character * >(o); ch->disconnected(); gameHandler->kill(ch); } delete o; break; case EVENT_INSERT: insertOrDelete(o); break; case EVENT_WARP: assert(o->getType() == OBJECT_CHARACTER); warp(static_cast< Character * >(o), e.map, e.x, e.y); break; } } delayedEvents.clear(); }
/** * Initializes the map content. This creates the warps, spawn areas, npcs and * other scripts. */ void MapComposite::initializeContent() { mContent = new MapContent(mMap); const std::vector<MapObject *> &objects = mMap->getObjects(); for (size_t i = 0; i < objects.size(); ++i) { const MapObject *object = objects.at(i); const std::string &type = object->getType(); if (utils::compareStrI(type, "WARP") == 0) { std::string destMapName = object->getProperty("DEST_MAP"); std::string destMapObjectName = object->getProperty("DEST_NAME"); MapComposite *destMap = MapManager::getMap(destMapName); int destX = 0; int destY = 0; if (destMap && !destMapObjectName.empty()) { const MapObject *obj = destMap->findMapObject(destMapObjectName, "WARP"); if (obj) { const Rectangle &rect = obj->getBounds(); destX = rect.x + rect.w / 2; destY = rect.y + rect.h / 2; } else { LOG_ERROR("Warp target \"" << destMapObjectName << "\" " << "was not found on the map " << destMap->getName()); } } else { destX = utils::stringToInt(object->getProperty("DEST_X")); destY = utils::stringToInt(object->getProperty("DEST_Y")); } if (destMap && destX && destY) { Entity *entity = new Entity(OBJECT_OTHER, this); WarpAction *action = new WarpAction(destMap, destX, destY); entity->addComponent( new TriggerAreaComponent(object->getBounds(), action, false)); insert(entity); } else { LOG_WARN("Unrecognized warp format on map " << mName); } } else if (utils::compareStrI(type, "SPAWN") == 0) { MonsterClass *monster = 0; int maxBeings = utils::stringToInt(object->getProperty("MAX_BEINGS")); int spawnRate = utils::stringToInt(object->getProperty("SPAWN_RATE")); std::string monsterName = object->getProperty("MONSTER_ID"); int monsterId = utils::stringToInt(monsterName); if (monsterId) { monster = monsterManager->getMonster(monsterId); if (!monster) { LOG_WARN("Couldn't find monster ID " << monsterId << " for spawn area"); } } else { monster = monsterManager->getMonsterByName(monsterName); if (!monster) { LOG_WARN("Couldn't find monster " << monsterName << " for spawn area"); } } if (monster && maxBeings && spawnRate) { Entity *entity = new Entity(OBJECT_OTHER, this); SpawnAreaComponent *spawnArea = new SpawnAreaComponent(monster, object->getBounds(), maxBeings, spawnRate); entity->addComponent(spawnArea); insert(entity); } } else if (utils::compareStrI(type, "NPC") == 0) { int npcId = utils::stringToInt(object->getProperty("NPC_ID")); std::string gender = object->getProperty("GENDER"); std::string scriptText = object->getProperty("SCRIPT"); if (npcId && !scriptText.empty()) { Script *script = ScriptManager::currentState(); script->loadNPC(object->getName(), npcId, ManaServ::getGender(gender), object->getX(), object->getY(), scriptText.c_str(), this); } else { LOG_WARN("Unrecognized format for npc"); } } else if (utils::compareStrI(type, "SCRIPT") == 0) { std::string scriptFilename = object->getProperty("FILENAME"); std::string scriptText = object->getProperty("TEXT"); Script *script = ScriptManager::currentState(); Script::Context context; context.map = this; if (!scriptFilename.empty()) { script->loadFile(scriptFilename, context); } else if (!scriptText.empty()) { std::string name = "'" + object->getName() + "'' in " + mName; script->load(scriptText.c_str(), name.c_str(), context); } else { LOG_WARN("Unrecognized format for script"); } } } }
static void handleCharWarp(Character *player, std::string &args) { int x, y; MapComposite *map; Character *other; // get the arguments std::string character = getArgument(args); std::string mapstr = getArgument(args); std::string xstr = getArgument(args); std::string ystr = getArgument(args); // if any of them are empty strings, no argument was given if (character.empty() || mapstr.empty() || xstr.empty() || ystr.empty()) { say("Invalid number of arguments given.", player); say("Usage: @warp <character> <map> <x> <y>", player); return; } // if it contains # then it means the player if (character == "#") { other = player; } else { // check for valid player other = gameHandler->getCharacterByNameSlow(character); if (!other) { say("Invalid or offline character <" + character + ">.", player); return; } } // if it contains # then it means the player's map if (mapstr == "#") { map = player->getMap(); } else { if (mapstr[0] == '#') { mapstr = mapstr.substr(1); // check for valid map id int id; if (!utils::isNumeric(mapstr)) { say("Invalid map", player); return; } id = utils::stringToInt(mapstr); // get the map map = MapManager::getMap(id); if (!map) { say("Invalid map", player); return; } } else { map = MapManager::getMap(mapstr); if (!map) { say("Invalid map", player); return; } } } if (!utils::isNumeric(xstr)) { say("Invalid x", player); return; } if (!utils::isNumeric(ystr)) { say("Invalid y", player); return; } // change the x and y to integers x = utils::stringToInt(xstr); y = utils::stringToInt(ystr); // now warp the player GameState::warp(other, map, x, y); // log transaction std::stringstream ss; ss << "User warped " << other->getName() << " to " << map->getName() << " (" << x << ", " << y << ")"; accountHandler->sendTransaction(player->getDatabaseID(), TRANS_CMD_WARP, ss.str()); }
void GameState::update(int worldTime) { # ifndef NDEBUG dbgLockObjects = true; # endif // Update game state (update AI, etc.) const MapManager::Maps &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); /* sending the whole character is overhead for the database, it should be replaced by a syncbuffer. see: game-server/accountconnection: AccountConnection::syncChanges() if (worldTime % 2000 == 0) { accountHandler->sendCharacterData(*p); } */ } for (ActorIterator i(map->getWholeMapIterator()); i; ++i) { Actor *a = *i; a->clearUpdateFlags(); if (a->canFight()) { static_cast< Being * >(a)->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) { const DelayedEvent &e = i->second; Actor *o = i->first; switch (e.type) { case EVENT_REMOVE: remove(o); if (o->getType() == OBJECT_CHARACTER) { Character *ch = static_cast< Character * >(o); ch->disconnected(); gameHandler->kill(ch); } delete o; break; case EVENT_INSERT: insertSafe(o); break; case EVENT_WARP: assert(o->getType() == OBJECT_CHARACTER); warp(static_cast< Character * >(o), e.map, e.x, e.y); break; } } delayedEvents.clear(); }
void AccountConnection::processMessage(MessageIn &msg) { LOG_DEBUG("Received message " << msg << " from account server"); switch (msg.getId()) { case AGMSG_REGISTER_RESPONSE: { if (msg.readInt16() != DATA_VERSION_OK) { LOG_ERROR("Item database is outdated! Please update to " "prevent inconsistencies"); stop(); // Disconnect gracefully from account server. // Stop gameserver to prevent inconsistencies. exit(EXIT_DB_EXCEPTION); } else { LOG_DEBUG("Local item database is " "in sync with account server."); } if (msg.readInt16() != PASSWORD_OK) { LOG_ERROR("This game server sent a invalid password"); stop(); exit(EXIT_BAD_CONFIG_PARAMETER); } // read world state variables while (msg.getUnreadLength()) { std::string key = msg.readString(); std::string value = msg.readString(); if (!key.empty() && !value.empty()) { GameState::setVariableFromDbserver(key, value); } } } break; case AGMSG_PLAYER_ENTER: { std::string token = msg.readString(MAGIC_TOKEN_LENGTH); Character *ptr = new Character(msg); gameHandler->addPendingCharacter(token, ptr); } break; case AGMSG_ACTIVE_MAP: { int mapId = msg.readInt16(); if (MapManager::activateMap(mapId)) { // Set map variables MapComposite *m = MapManager::getMap(mapId); int mapVarsNumber = msg.readInt16(); for(int i = 0; i < mapVarsNumber; ++i) { std::string key = msg.readString(); std::string value = msg.readString(); if (!key.empty() && !value.empty()) m->setVariableFromDbserver(key, value); } // Recreate potential persistent floor items LOG_DEBUG("Recreate persistant items on map " << mapId); int floorItemsNumber = msg.readInt16(); for (int i = 0; i < floorItemsNumber; ++i) { int itemId = msg.readInt32(); int amount = msg.readInt16(); int posX = msg.readInt16(); int posY = msg.readInt16(); if (ItemClass *ic = itemManager->getItem(itemId)) { Item *item = new Item(ic, amount); item->setMap(m); Point dst(posX, posY); item->setPosition(dst); if (!GameState::insertOrDelete(item)) { // The map is full. LOG_WARN("Couldn't add floor item(s) " << itemId << " into map " << mapId); return; } } } } } break; case AGMSG_SET_VAR_WORLD: { std::string key = msg.readString(); std::string value = msg.readString(); GameState::setVariableFromDbserver(key, value); LOG_DEBUG("Global variable \"" << key << "\" has changed to \"" << value << "\""); } break; case AGMSG_REDIRECT_RESPONSE: { int id = msg.readInt32(); std::string token = msg.readString(MAGIC_TOKEN_LENGTH); std::string address = msg.readString(); int port = msg.readInt16(); gameHandler->completeServerChange(id, token, address, port); } break; case AGMSG_GET_VAR_CHR_RESPONSE: { int id = msg.readInt32(); std::string name = msg.readString(); std::string value = msg.readString(); recoveredQuestVar(id, name, value); } break; case CGMSG_CHANGED_PARTY: { // Character DB id int charid = msg.readInt32(); // Party id, 0 for none int partyid = msg.readInt32(); gameHandler->updateCharacter(charid, partyid); } break; case CGMSG_POST_RESPONSE: { // get the character Character *character = postMan->getCharacter(msg.readInt32()); // check character is still valid if (!character) { break; } std::string sender = msg.readString(); std::string letter = msg.readString(); postMan->gotPost(character, sender, letter); } break; case CGMSG_STORE_POST_RESPONSE: { // get character Character *character = postMan->getCharacter(msg.readInt32()); // check character is valid if (!character) { break; } // TODO: Get NPC to tell character if the sending of post // was successful or not } break; default: LOG_WARN("Invalid message type"); break; } }
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(); if (say.empty()) break; if (say[0] == '@') { CommandHandler::handleCommand(computer.character, say); break; } GameState::sayAround(computer.character, say); std::string msg = computer.character->getName() + " said " + say; accountHandler->sendTransaction(computer.character->getDatabaseID(), TRANS_MSG_PUBLIC, msg); } break; case PGMSG_NPC_TALK: case PGMSG_NPC_TALK_NEXT: case PGMSG_NPC_SELECT: case PGMSG_NPC_NUMBER: case PGMSG_NPC_STRING: { int id = message.readShort(); Actor *o = findActorNear(computer.character, id); if (!o || o->getType() != OBJECT_NPC) { sendError(comp, id, "Not close enough to NPC\n"); break; } NPC *q = static_cast< NPC * >(o); if (message.getId() == PGMSG_NPC_SELECT) { q->select(computer.character, message.readByte()); } else if (message.getId() == PGMSG_NPC_NUMBER) { q->integerReceived(computer.character, message.readLong()); } else if (message.getId() == PGMSG_NPC_STRING) { q->stringReceived(computer.character, message.readString()); } 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 (FixedActorIterator i(map->getAroundPointIterator(ipos, 0)); i; ++i) { Actor *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); // log transaction std::stringstream str; str << "User picked up item " << ic->getDatabaseID() << " at " << opos.x << "x" << opos.y; accountHandler->sendTransaction(computer.character->getDatabaseID(), TRANS_ITEM_PICKUP, str.str()); break; } } } } break; case PGMSG_USE_ITEM: { int slot = message.readByte(); Inventory inv(computer.character); if (ItemClass *ic = ItemManager::getItem(inv.getItem(slot))) { if (ic->use(computer.character)) { inv.removeFromSlot(slot, 1); // log transaction std::stringstream str; str << "User used item " << ic->getDatabaseID() << " from slot " << slot; accountHandler->sendTransaction(computer.character->getDatabaseID(), TRANS_ITEM_USED, str.str()); } } } 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()); if (!GameState::insert(item)) { // The map is full. Put back into inventory. inv.insert(ic->getDatabaseID(), amount - nb); delete item; break; } // log transaction Point pt = computer.character->getPosition(); std::stringstream str; str << "User dropped item " << ic->getDatabaseID() << " at " << pt.x << "x" << pt.y; accountHandler->sendTransaction(computer.character->getDatabaseID(), TRANS_ITEM_DROP, str.str()); } } break; case PGMSG_WALK: { handleWalk(&computer, message); } break; case PGMSG_EQUIP: { int slot = message.readByte(); Inventory(computer.character).equip(slot); } break; case PGMSG_UNEQUIP: { int slot = message.readByte(); if (slot >= 0 && slot < EQUIP_PROJECTILE_SLOT) { Inventory(computer.character).unequip(slot); } } break; case PGMSG_MOVE_ITEM: { int slot1 = message.readByte(); int slot2 = message.readByte(); int amount = message.readByte(); Inventory(computer.character).move(slot1, slot2, amount); // log transaction std::stringstream str; str << "User moved item " << " from slot " << slot1 << " to slot " << slot2; accountHandler->sendTransaction(computer.character->getDatabaseID(), TRANS_ITEM_MOVE, str.str()); } break; case PGMSG_ATTACK: { int id = message.readShort(); LOG_DEBUG("Character " << computer.character->getPublicID() << " attacked being " << id); Actor *o = findActorNear(computer.character, id); if (o && o->getType() != OBJECT_NPC) { Being *being = static_cast<Being*>(o); computer.character->setTarget(being); computer.character->setAction(Being::ATTACK); } } break; case PGMSG_USE_SPECIAL: { int specialID = message.readByte(); LOG_DEBUG("Character " << computer.character->getPublicID() << " tries to use his special attack "<<specialID); computer.character->useSpecial(specialID); } case PGMSG_ACTION_CHANGE: { Being::Action action = (Being::Action)message.readByte(); Being::Action current = (Being::Action)computer.character->getAction(); bool logActionChange = true; switch (action) { case Being::STAND: { if (current == Being::SIT) { computer.character->setAction(Being::STAND); logActionChange = false; } } break; case Being::SIT: { if (current == Being::STAND) { computer.character->setAction(Being::SIT); logActionChange = false; } } break; default: break; } // Log the action change only when this is relevant. if (logActionChange) { // log transaction std::stringstream str; str << "User changed action from " << current << " to " << action; accountHandler->sendTransaction( computer.character->getDatabaseID(), TRANS_ACTION_CHANGE, str.str()); } } break; case PGMSG_DIRECTION_CHANGE: { computer.character->setDirection(message.readByte()); } 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 computer.character->disconnected(); 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->isBusy()) { result.writeShort(GPMSG_TRADE_CANCEL); break; } new Trade(computer.character, q); // log transaction std::string str; str = "User requested trade with " + q->getName(); accountHandler->sendTransaction(computer.character->getDatabaseID(), TRANS_TRADE_REQUEST, str); } break; case PGMSG_TRADE_CANCEL: case PGMSG_TRADE_AGREED: case PGMSG_TRADE_CONFIRM: case PGMSG_TRADE_ADD_ITEM: case PGMSG_TRADE_SET_MONEY: { std::stringstream str; Trade *t = computer.character->getTrading(); if (!t) break; switch (message.getId()) { case PGMSG_TRADE_CANCEL: t->cancel(); break; case PGMSG_TRADE_CONFIRM: t->confirm(computer.character); break; case PGMSG_TRADE_AGREED: t->agree(computer.character); // log transaction accountHandler->sendTransaction(computer.character->getDatabaseID(), TRANS_TRADE_END, "User finished trading"); break; case PGMSG_TRADE_SET_MONEY: { int money = message.readLong(); t->setMoney(computer.character, money); // log transaction str << "User added " << money << " money to trade."; accountHandler->sendTransaction(computer.character->getDatabaseID(), TRANS_TRADE_MONEY, str.str()); } break; case PGMSG_TRADE_ADD_ITEM: { int slot = message.readByte(); t->addItem(computer.character, slot, message.readByte()); // log transaction str << "User add item from slot " << slot; accountHandler->sendTransaction(computer.character->getDatabaseID(), TRANS_TRADE_ITEM, str.str()); } break; } } break; case PGMSG_NPC_BUYSELL: { BuySell *t = computer.character->getBuySell(); if (!t) break; int id = message.readShort(); int amount = message.readShort(); t->perform(id, amount); } break; case PGMSG_RAISE_ATTRIBUTE: { int attribute = message.readByte(); AttribmodResponseCode retCode; retCode = computer.character->useCharacterPoint(attribute); result.writeShort(GPMSG_RAISE_ATTRIBUTE_RESPONSE); result.writeByte(retCode); result.writeByte(attribute); if (retCode == ATTRIBMOD_OK ) { accountHandler->updateCharacterPoints( computer.character->getDatabaseID(), computer.character->getCharacterPoints(), computer.character->getCorrectionPoints(), attribute, computer.character->getAttribute(attribute)); // log transaction std::stringstream str; str << "User increased attribute " << attribute; accountHandler->sendTransaction(computer.character->getDatabaseID(), TRANS_ATTR_INCREASE, str.str()); } } break; case PGMSG_LOWER_ATTRIBUTE: { int attribute = message.readByte(); AttribmodResponseCode retCode; retCode = computer.character->useCorrectionPoint(attribute); result.writeShort(GPMSG_LOWER_ATTRIBUTE_RESPONSE); result.writeByte(retCode); result.writeByte(attribute); if (retCode == ATTRIBMOD_OK ) { accountHandler->updateCharacterPoints( computer.character->getDatabaseID(), computer.character->getCharacterPoints(), computer.character->getCorrectionPoints(), attribute, computer.character->getAttribute(attribute)); // log transaction std::stringstream str; str << "User decreased attribute " << attribute; accountHandler->sendTransaction(computer.character->getDatabaseID(), TRANS_ATTR_DECREASE, str.str()); } } break; case PGMSG_RESPAWN: { computer.character->respawn(); // plausibility check is done by character class } break; case PGMSG_NPC_POST_SEND: { handleSendPost(&computer, message); } break; default: LOG_WARN("Invalid message type"); result.writeShort(XXMSG_INVALID); break; } if (result.getLength() > 0) computer.send(result); }