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 void handleDrop(Character *player, std::string &args) { ItemClass *ic; int value = 0; // get arguments std::string itemclass = getArgument(args); std::string valuestr = getArgument(args); // check all arguments are there if (itemclass.empty()) { say("Invalid number of arguments given.", player); say("Usage: @drop <item> [amount]", player); return; } // identify the item type if (utils::isNumeric(itemclass)) { int id = utils::stringToInt(itemclass); ic = itemManager->getItem(id); } else { ic = itemManager->getItemByName(itemclass); } if (!ic) { say("Invalid item", player); return; } //identify the amount if (valuestr.empty()) { value = 1; } else if (utils::isNumeric(valuestr)) { value = utils::stringToInt(valuestr); } // check for valid amount if (value <= 0) { say("Invalid number of items", player); return; } // create the integer and put it on the map Item *item = new Item(ic, value); item->setMap(player->getMap()); item->setPosition(player->getPosition()); GameState::insertOrDelete(item); // log transaction std::stringstream str; str << "User created item " << ic->getDatabaseID(); accountHandler->sendTransaction(player->getDatabaseID(), TRANS_CMD_DROP, str.str()); }
/** * Informs a player of what happened around the character. */ static void informPlayer(MapComposite *map, Character *p) { MessageOut moveMsg(GPMSG_BEINGS_MOVE); MessageOut damageMsg(GPMSG_BEINGS_DAMAGE); const Point &pold = p->getOldPosition(), ppos = p->getPosition(); int pid = p->getPublicID(), pflags = p->getUpdateFlags(); int visualRange = Configuration::getValue("game_visualRange", 448); // Inform client about activities of other beings near its character for (BeingIterator it(map->getAroundBeingIterator(p, visualRange)); it; ++it) { Being *o = *it; const Point &oold = o->getOldPosition(), opos = o->getPosition(); int otype = o->getType(); int oid = o->getPublicID(), oflags = o->getUpdateFlags(); int flags = 0; // Check if the character p and the moving object o are around. bool wereInRange = pold.inRangeOf(oold, visualRange) && !((pflags | oflags) & UPDATEFLAG_NEW_ON_MAP); bool willBeInRange = ppos.inRangeOf(opos, visualRange); if (!wereInRange && !willBeInRange) { // Nothing to report: o and p are far away from each other. continue; } if (wereInRange && willBeInRange) { // Send attack messages. if ((oflags & UPDATEFLAG_ATTACK) && oid != pid) { MessageOut AttackMsg(GPMSG_BEING_ATTACK); AttackMsg.writeInt16(oid); AttackMsg.writeInt8(o->getDirection()); AttackMsg.writeInt8(static_cast< Being * >(o)->getAttackId()); gameHandler->sendTo(p, AttackMsg); } // Send action change messages. if ((oflags & UPDATEFLAG_ACTIONCHANGE)) { MessageOut ActionMsg(GPMSG_BEING_ACTION_CHANGE); ActionMsg.writeInt16(oid); ActionMsg.writeInt8(static_cast< Being * >(o)->getAction()); gameHandler->sendTo(p, ActionMsg); } // Send looks change messages. if (oflags & UPDATEFLAG_LOOKSCHANGE) { MessageOut LooksMsg(GPMSG_BEING_LOOKS_CHANGE); LooksMsg.writeInt16(oid); Character * c = static_cast<Character * >(o); serializeLooks(c, LooksMsg); LooksMsg.writeInt16(c->getHairStyle()); LooksMsg.writeInt16(c->getHairColor()); LooksMsg.writeInt16(c->getGender()); gameHandler->sendTo(p, LooksMsg); } // Send emote messages. if (oflags & UPDATEFLAG_EMOTE) { int emoteId = o->getLastEmote(); if (emoteId > -1) { MessageOut EmoteMsg(GPMSG_BEING_EMOTE); EmoteMsg.writeInt16(oid); EmoteMsg.writeInt16(emoteId); gameHandler->sendTo(p, EmoteMsg); } } // Send direction change messages. if (oflags & UPDATEFLAG_DIRCHANGE) { MessageOut DirMsg(GPMSG_BEING_DIR_CHANGE); DirMsg.writeInt16(oid); DirMsg.writeInt8(o->getDirection()); gameHandler->sendTo(p, DirMsg); } // Send damage messages. if (o->canFight()) { Being *victim = static_cast< Being * >(o); const Hits &hits = victim->getHitsTaken(); for (Hits::const_iterator j = hits.begin(), j_end = hits.end(); j != j_end; ++j) { damageMsg.writeInt16(oid); damageMsg.writeInt16(*j); } } if (oold == opos) { // o does not move, nothing more to report. continue; } } if (!willBeInRange) { // o is no longer visible from p. Send leave message. MessageOut leaveMsg(GPMSG_BEING_LEAVE); leaveMsg.writeInt16(oid); gameHandler->sendTo(p, leaveMsg); continue; } if (!wereInRange) { // o is now visible by p. Send enter message. MessageOut enterMsg(GPMSG_BEING_ENTER); enterMsg.writeInt8(otype); enterMsg.writeInt16(oid); enterMsg.writeInt8(static_cast< Being *>(o)->getAction()); enterMsg.writeInt16(opos.x); enterMsg.writeInt16(opos.y); enterMsg.writeInt8(o->getDirection()); enterMsg.writeInt8(o->getGender()); switch (otype) { case OBJECT_CHARACTER: { Character *q = static_cast< Character * >(o); enterMsg.writeString(q->getName()); enterMsg.writeInt8(q->getHairStyle()); enterMsg.writeInt8(q->getHairColor()); serializeLooks(q, enterMsg); } break; case OBJECT_MONSTER: { Monster *q = static_cast< Monster * >(o); enterMsg.writeInt16(q->getSpecy()->getId()); enterMsg.writeString(q->getName()); } break; case OBJECT_NPC: { NpcComponent *npcComponent = o->getComponent<NpcComponent>(); enterMsg.writeInt16(npcComponent->getNpcId()); enterMsg.writeString(o->getName()); } break; default: assert(false); // TODO break; } gameHandler->sendTo(p, enterMsg); } if (opos != oold) { // Add position check coords every 5 seconds. if (currentTick % 50 == 0) flags |= MOVING_POSITION; flags |= MOVING_DESTINATION; } // Send move messages. moveMsg.writeInt16(oid); moveMsg.writeInt8(flags); if (flags & MOVING_POSITION) { moveMsg.writeInt16(oold.x); moveMsg.writeInt16(oold.y); } if (flags & MOVING_DESTINATION) { moveMsg.writeInt16(opos.x); moveMsg.writeInt16(opos.y); // We multiply the sent speed (in tiles per second) by ten // to get it within a byte with decimal precision. // For instance, a value of 4.5 will be sent as 45. moveMsg.writeInt8((unsigned short) (o->getModifiedAttribute(ATTR_MOVE_SPEED_TPS) * 10)); } } // 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 status change. p->sendStatus(); // Inform client about health change of party members for (CharacterIterator i(map->getWholeMapIterator()); i; ++i) { Character *c = *i; // Make sure its not the same character if (c == p) continue; // make sure they are in the same party if (c->getParty() == p->getParty()) { int cflags = c->getUpdateFlags(); if (cflags & UPDATEFLAG_HEALTHCHANGE) { MessageOut healthMsg(GPMSG_BEING_HEALTH_CHANGE); healthMsg.writeInt16(c->getPublicID()); healthMsg.writeInt16(c->getModifiedAttribute(ATTR_HP)); healthMsg.writeInt16(c->getModifiedAttribute(ATTR_MAX_HP)); gameHandler->sendTo(p, healthMsg); } } } // Inform client about items on the ground around its character MessageOut itemMsg(GPMSG_ITEMS); for (FixedActorIterator it(map->getAroundBeingIterator(p, visualRange)); it; ++it) { Actor *o = *it; assert(o->getType() == OBJECT_ITEM || o->getType() == OBJECT_EFFECT); Point opos = o->getPosition(); int oflags = o->getUpdateFlags(); bool willBeInRange = ppos.inRangeOf(opos, visualRange); bool wereInRange = pold.inRangeOf(opos, visualRange) && !((pflags | oflags) & UPDATEFLAG_NEW_ON_MAP); if (willBeInRange ^ wereInRange) { switch (o->getType()) { case OBJECT_ITEM: { ItemComponent *item = o->getComponent<ItemComponent>(); ItemClass *itemClass = item->getItemClass(); if (oflags & UPDATEFLAG_NEW_ON_MAP) { /* Send a specific message to the client when an item appears out of nowhere, so that a sound/animation can be performed. */ MessageOut appearMsg(GPMSG_ITEM_APPEAR); appearMsg.writeInt16(itemClass->getDatabaseID()); appearMsg.writeInt16(opos.x); appearMsg.writeInt16(opos.y); gameHandler->sendTo(p, appearMsg); } else { itemMsg.writeInt16(willBeInRange ? itemClass->getDatabaseID() : 0); itemMsg.writeInt16(opos.x); itemMsg.writeInt16(opos.y); } } break; case OBJECT_EFFECT: { EffectComponent *e = o->getComponent<EffectComponent>(); e->setShown(); // Don't show old effects if (!(oflags & UPDATEFLAG_NEW_ON_MAP)) break; if (Being *b = e->getBeing()) { MessageOut effectMsg(GPMSG_CREATE_EFFECT_BEING); effectMsg.writeInt16(e->getEffectId()); effectMsg.writeInt16(b->getPublicID()); gameHandler->sendTo(p, effectMsg); } else { MessageOut effectMsg(GPMSG_CREATE_EFFECT_POS); effectMsg.writeInt16(e->getEffectId()); effectMsg.writeInt16(opos.x); effectMsg.writeInt16(opos.y); gameHandler->sendTo(p, effectMsg); } } break; default: break; } // Switch } } // Do not send a packet if nothing happened in p's range. if (itemMsg.getLength() > 2) gameHandler->sendTo(p, itemMsg); }
static void handleItem(Character *player, std::string &args) { Character *other; ItemClass *ic; int value = 0; // get arguments std::string character = getArgument(args); std::string itemclass = getArgument(args); std::string valuestr = getArgument(args); // check all arguments are there if (character.empty() || itemclass.empty()) { say("Invalid number of arguments given.", player); say("Usage: @item <character> <item> [amount]", player); return; } // if it contains # that means the player if (character == "#") { other = player; } else { // check for valid player other = gameHandler->getCharacterByNameSlow(character); if (!other) { say("Invalid character or they are offline", player); return; } } // identify the item type if (utils::isNumeric(itemclass)) { int id = utils::stringToInt(itemclass); ic = itemManager->getItem(id); } else { ic = itemManager->getItemByName(itemclass); } if (!ic) { say("Invalid item", player); return; } //identify the amount if (valuestr.empty()) { value = 1; } else if (utils::isNumeric(valuestr)) { value = utils::stringToInt(valuestr); } // check for valid amount if (value <= 0) { say("Invalid number of items", player); return; } // insert the item into the inventory Inventory(other).insert(ic->getDatabaseID(), value); // log transaction std::stringstream str; str << "User created item " << ic->getDatabaseID(); accountHandler->sendTransaction(player->getDatabaseID(), TRANS_CMD_ITEM, str.str()); }
static void handleCraft(Character *player, std::string &args) { std::stringstream errMsg; std::list<InventoryItem> recipe; Inventory playerInventory(player); std::map<int, int> totalAmountOfItem; while (true) { // parsing std::string strItem = getArgument(args); ItemClass* item = itemManager->getItemByName(strItem); std::string strAmount = getArgument(args); int amount = utils::stringToInt(strAmount); // syntax error checking if (strItem.empty()) { // the item list has ended break; } if (!item) { // item wasn't found in the item database errMsg << "Unknown item: \"" << strItem << "\"."; break; } if (strAmount.empty()) { // the last item in the list has no amount defined errMsg << "No amount given for \"" << strItem << "\"."; break; } if (amount < 1) { errMsg << "Illegal amount \""<< strAmount << "\" for item \"" << strItem << "\"."; break; } // inventory checking int available = playerInventory.count(item->getDatabaseID()); if (available == 0) { errMsg << "You have no "<< strItem << " in your inventory."; break; } if (available < amount) { errMsg << "You haven't got that many "<< strItem << "s in your inventory."; break; } // when there is still no break, add the item; InventoryItem recipeItem; recipeItem.itemId = item->getDatabaseID(); recipeItem.amount = amount; recipe.push_back(recipeItem); } if (!errMsg.str().empty()) { // when an error occured, output the error say(errMsg.str(), player); return; } else { // pass to script engine. The engine is responsible for all // further processing of the crafting operation, including // outputting an error message when the recipe is invalid. ScriptManager::performCraft(player, recipe); } }
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); }