void ChatHandler::sayToPlayer(ChatClient &computer, const std::string &playerName, const std::string &text) { MessageOut result; LOG_DEBUG(computer.characterName << " says to " << playerName << ": " << text); // Send it to the being if the being exists result.writeShort(CPMSG_PRIVMSG); result.writeString(computer.characterName); result.writeString(text); for (NetComputers::iterator i = clients.begin(), i_end = clients.end(); i != i_end; ++i) { if (static_cast< ChatClient * >(*i)->characterName == playerName) { (*i)->send(result); break; } } }
void Server::generalChatMsg(Uint32 sender, std::string msg){ if (chat_mode && sender != 0) // The sender 0 (zero) is the server itself! std::cout << msg << std::endl; std::map<Uint32, Client*>::iterator p; for (p = mClients.begin(); p != mClients.end(); p++){ if(p->first != sender){ // don't sentd the message to the original sender. MessageOut* messageout = new MessageOut(MSG_CHAT); messageout->writeString(msg); messageout->addCRC(); Connection::putMessage(p->second->getSocket(), messageout); delete messageout; } } }
void CharacterComponent::serialize(Entity &entity, MessageOut &msg) { auto *beingComponent = entity.getComponent<BeingComponent>(); // general character properties msg.writeInt8(getAccountLevel()); msg.writeInt8(beingComponent->getGender()); msg.writeInt8(getHairStyle()); msg.writeInt8(getHairColor()); msg.writeInt16(getAttributePoints()); msg.writeInt16(getCorrectionPoints()); const AttributeMap &attributes = beingComponent->getAttributes(); std::map<const AttributeInfo *, const Attribute *> attributesToSend; for (auto &attributeIt : attributes) { if (attributeIt.first->persistent) attributesToSend.insert(std::make_pair(attributeIt.first, &attributeIt.second)); } msg.writeInt16(attributesToSend.size()); for (auto &attributeIt : attributesToSend) { msg.writeInt16(attributeIt.first->id); msg.writeDouble(attributeIt.second->getBase()); msg.writeDouble(attributeIt.second->getModifiedAttribute()); } // status effects currently affecting the character auto &statusEffects = beingComponent->getStatusEffects(); msg.writeInt16(statusEffects.size()); for (auto &statusIt : statusEffects) { msg.writeInt16(statusIt.first); msg.writeInt16(statusIt.second.time); } // location msg.writeInt16(entity.getMap()->getID()); const Point &pos = entity.getComponent<ActorComponent>()->getPosition(); msg.writeInt16(pos.x); msg.writeInt16(pos.y); // kill count msg.writeInt16(getKillCountSize()); for (auto &killCountIt : mKillCount) { msg.writeInt16(killCountIt.first); msg.writeInt32(killCountIt.second); } // character abilities auto &abilities = entity.getComponent<AbilityComponent>()->getAbilities(); msg.writeInt16(abilities.size()); for (auto &abilityIt : abilities) { msg.writeInt32(abilityIt.first); } // questlog msg.writeInt16(mQuestlog.size()); for (auto questlogIt : mQuestlog) { QuestInfo &quest = questlogIt.second; msg.writeInt16(quest.id); msg.writeInt8(quest.state); msg.writeString(quest.title); msg.writeString(quest.description); } // inventory - must be last because size isn't transmitted const Possessions &poss = getPossessions(); const InventoryData &inventoryData = poss.getInventory(); for (InventoryData::const_iterator itemIt = inventoryData.begin(), itemIt_end = inventoryData.end(); itemIt != itemIt_end; ++itemIt) { msg.writeInt16(itemIt->first); // slot id msg.writeInt16(itemIt->second.itemId); msg.writeInt16(itemIt->second.amount); msg.writeInt8(itemIt->second.equipmentSlot); } }
void ServerHandler::processMessage(NetComputer *comp, MessageIn &msg) { MessageOut result; GameServer *server = static_cast<GameServer *>(comp); switch (msg.getId()) { case GAMSG_REGISTER: { LOG_DEBUG("GAMSG_REGISTER"); // TODO: check the credentials of the game server server->address = msg.readString(); server->port = msg.readInt16(); const std::string password = msg.readString(); // checks the version of the remote item database with our local copy unsigned int dbversion = msg.readInt32(); LOG_INFO("Game server uses itemsdatabase with version " << dbversion); LOG_DEBUG("AGMSG_REGISTER_RESPONSE"); MessageOut outMsg(AGMSG_REGISTER_RESPONSE); if (dbversion == storage->getItemDatabaseVersion()) { LOG_DEBUG("Item databases between account server and " "gameserver are in sync"); outMsg.writeInt16(DATA_VERSION_OK); } else { LOG_DEBUG("Item database of game server has a wrong version"); outMsg.writeInt16(DATA_VERSION_OUTDATED); } if (password == Configuration::getValue("net_password", "changeMe")) { outMsg.writeInt16(PASSWORD_OK); comp->send(outMsg); // transmit global world state variables std::map<std::string, std::string> variables; variables = storage->getAllWorldStateVars(0); for (std::map<std::string, std::string>::iterator i = variables.begin(); i != variables.end(); i++) { outMsg.writeString(i->first); outMsg.writeString(i->second); } } else { LOG_INFO("The password given by " << server->address << ':' << server->port << " was bad."); outMsg.writeInt16(PASSWORD_BAD); comp->disconnect(outMsg); break; } LOG_INFO("Game server " << server->address << ':' << server->port << " wants to register " << (msg.getUnreadLength() / 2) << " maps."); while (msg.getUnreadLength()) { int id = msg.readInt16(); LOG_INFO("Registering map " << id << '.'); if (GameServer *s = getGameServerFromMap(id)) { LOG_ERROR("Server Handler: map is already registered by " << s->address << ':' << s->port << '.'); } else { MessageOut outMsg(AGMSG_ACTIVE_MAP); outMsg.writeInt16(id); std::map<std::string, std::string> variables; variables = storage->getAllWorldStateVars(id); for (std::map<std::string, std::string>::iterator i = variables.begin(); i != variables.end(); i++) { outMsg.writeString(i->first); outMsg.writeString(i->second); } comp->send(outMsg); MapStatistics &m = server->maps[id]; m.nbThings = 0; m.nbMonsters = 0; } } } break; case GAMSG_PLAYER_DATA: { LOG_DEBUG("GAMSG_PLAYER_DATA"); int id = msg.readInt32(); if (Character *ptr = storage->getCharacter(id, NULL)) { deserializeCharacterData(*ptr, msg); if (!storage->updateCharacter(ptr)) { LOG_ERROR("Failed to update character " << id << '.'); } delete ptr; } else { LOG_ERROR("Received data for non-existing character " << id << '.'); } } break; case GAMSG_PLAYER_SYNC: { LOG_DEBUG("GAMSG_PLAYER_SYNC"); GameServerHandler::syncDatabase(msg); } break; case GAMSG_REDIRECT: { LOG_DEBUG("GAMSG_REDIRECT"); int id = msg.readInt32(); std::string magic_token(utils::getMagicToken()); if (Character *ptr = storage->getCharacter(id, NULL)) { int mapId = ptr->getMapId(); if (GameServer *s = getGameServerFromMap(mapId)) { registerGameClient(s, magic_token, ptr); result.writeInt16(AGMSG_REDIRECT_RESPONSE); result.writeInt32(id); result.writeString(magic_token, MAGIC_TOKEN_LENGTH); result.writeString(s->address); result.writeInt16(s->port); } else { LOG_ERROR("Server Change: No game server for map " << mapId << '.'); } delete ptr; } else { LOG_ERROR("Received data for non-existing character " << id << '.'); } } break; case GAMSG_PLAYER_RECONNECT: { LOG_DEBUG("GAMSG_PLAYER_RECONNECT"); int id = msg.readInt32(); std::string magic_token = msg.readString(MAGIC_TOKEN_LENGTH); if (Character *ptr = storage->getCharacter(id, NULL)) { int accountID = ptr->getAccountID(); AccountClientHandler::prepareReconnect(magic_token, accountID); delete ptr; } else { LOG_ERROR("Received data for non-existing character " << id << '.'); } } break; case GAMSG_GET_VAR_CHR: { int id = msg.readInt32(); std::string name = msg.readString(); std::string value = storage->getQuestVar(id, name); result.writeInt16(AGMSG_GET_VAR_CHR_RESPONSE); result.writeInt32(id); result.writeString(name); result.writeString(value); } break; case GAMSG_SET_VAR_CHR: { int id = msg.readInt32(); std::string name = msg.readString(); std::string value = msg.readString(); storage->setQuestVar(id, name, value); } break; case GAMSG_SET_VAR_WORLD: { std::string name = msg.readString(); std::string value = msg.readString(); // save the new value to the database storage->setWorldStateVar(name, value); // relay the new value to all gameservers for (ServerHandler::NetComputers::iterator i = clients.begin(); i != clients.end(); i++) { MessageOut varUpdateMessage(AGMSG_SET_VAR_WORLD); varUpdateMessage.writeString(name); varUpdateMessage.writeString(value); (*i)->send(varUpdateMessage); } } break; case GAMSG_SET_VAR_MAP: { int mapid = msg.readInt32(); std::string name = msg.readString(); std::string value = msg.readString(); storage->setWorldStateVar(name, mapid, value); } break; case GAMSG_BAN_PLAYER: { int id = msg.readInt32(); int duration = msg.readInt32(); storage->banCharacter(id, duration); } break; case GAMSG_CHANGE_PLAYER_LEVEL: { int id = msg.readInt32(); int level = msg.readInt16(); storage->setPlayerLevel(id, level); } break; case GAMSG_CHANGE_ACCOUNT_LEVEL: { int id = msg.readInt32(); int level = msg.readInt16(); // get the character so we can get the account id Character *c = storage->getCharacter(id, NULL); if (c) { storage->setAccountLevel(c->getAccountID(), level); } } break; case GAMSG_STATISTICS: { while (msg.getUnreadLength()) { int mapId = msg.readInt16(); ServerStatistics::iterator i = server->maps.find(mapId); if (i == server->maps.end()) { LOG_ERROR("Server " << server->address << ':' << server->port << " should not be sending stati" "stics for map " << mapId << '.'); // Skip remaining data. break; } MapStatistics &m = i->second; m.nbThings = msg.readInt16(); m.nbMonsters = msg.readInt16(); int nb = msg.readInt16(); m.players.resize(nb); for (int j = 0; j < nb; ++j) { m.players[j] = msg.readInt32(); } } } break; case GCMSG_REQUEST_POST: { // Retrieve the post for user LOG_DEBUG("GCMSG_REQUEST_POST"); result.writeInt16(CGMSG_POST_RESPONSE); // get the character id int characterId = msg.readInt32(); // send the character id of sender result.writeInt32(characterId); // get the character based on the id Character *ptr = storage->getCharacter(characterId, NULL); if (!ptr) { // Invalid character LOG_ERROR("Error finding character id for post"); break; } // get the post for that character Post *post = postalManager->getPost(ptr); // send the post if valid if (post) { for (unsigned int i = 0; i < post->getNumberOfLetters(); ++i) { // get each letter, send the sender's name, // the contents and any attachments Letter *letter = post->getLetter(i); result.writeString(letter->getSender()->getName()); result.writeString(letter->getContents()); std::vector<InventoryItem> items = letter->getAttachments(); for (unsigned int j = 0; j < items.size(); ++j) { result.writeInt16(items[j].itemId); result.writeInt16(items[j].amount); } } // clean up postalManager->clearPost(ptr); } } break; case GCMSG_STORE_POST: { // Store the letter for the user LOG_DEBUG("GCMSG_STORE_POST"); result.writeInt16(CGMSG_STORE_POST_RESPONSE); // get the sender and receiver int senderId = msg.readInt32(); std::string receiverName = msg.readString(); // for sending it back result.writeInt32(senderId); // get their characters Character *sender = storage->getCharacter(senderId, NULL); Character *receiver = storage->getCharacter(receiverName); if (!sender || !receiver) { // Invalid character LOG_ERROR("Error finding character id for post"); result.writeInt8(ERRMSG_INVALID_ARGUMENT); break; } // get the letter contents std::string contents = msg.readString(); std::vector< std::pair<int, int> > items; while (msg.getUnreadLength()) { items.push_back(std::pair<int, int>(msg.readInt16(), msg.readInt16())); } // save the letter LOG_DEBUG("Creating letter"); Letter *letter = new Letter(0, sender, receiver); letter->addText(contents); for (unsigned int i = 0; i < items.size(); ++i) { InventoryItem item; item.itemId = items[i].first; item.amount = items[i].second; letter->addAttachment(item); } postalManager->addLetter(letter); result.writeInt8(ERRMSG_OK); } break; case GAMSG_TRANSACTION: { LOG_DEBUG("TRANSACTION"); int id = msg.readInt32(); int action = msg.readInt32(); std::string message = msg.readString(); Transaction trans; trans.mCharacterId = id; trans.mAction = action; trans.mMessage = message; storage->addTransaction(trans); } break; case GCMSG_PARTY_INVITE: chatHandler->handlePartyInvite(msg); break; default: LOG_WARN("ServerHandler::processMessage, Invalid message type: " << msg.getId()); result.writeInt16(XXMSG_INVALID); break; } // return result if (result.getLength() > 0) comp->send(result); }
void ChatHandler::processRaw(MessageOut &outMsg, const std::string &line) { size_t pos = line.find(":"); if (pos == std::string::npos) { const int i = atoi(line.c_str()); if (line.length() <= 3) outMsg.writeInt8(static_cast<unsigned char>(i)); else if (line.length() <= 5) outMsg.writeInt16(static_cast<int16_t>(i)); else outMsg.writeInt32(i); } else { const std::string header = line.substr(0, pos); if (header.length() != 1) return; std::string data = line.substr(pos + 1); int i = 0; switch (header[0]) { case '1': case '2': case '4': i = atoi(data.c_str()); break; default: break; } switch (header[0]) { case '1': outMsg.writeInt8(static_cast<unsigned char>(i)); break; case '2': outMsg.writeInt16(static_cast<int16_t>(i)); break; case '4': outMsg.writeInt32(i); break; case 'c': { pos = line.find(","); if (pos != std::string::npos) { const uint16_t x = static_cast<const uint16_t>( atoi(data.substr(0, pos).c_str())); data = data.substr(pos + 1); pos = line.find(","); if (pos == std::string::npos) break; const uint16_t y = static_cast<const uint16_t>( atoi(data.substr(0, pos).c_str())); const int dir = atoi(data.substr(pos + 1).c_str()); outMsg.writeCoordinates(x, y, static_cast<unsigned char>(dir)); } break; } case 't': outMsg.writeString(data, static_cast<int>(data.length())); break; default: 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); }