From 3eeae12c498d1a4dbe969462d2ba841f77ee3ccb Mon Sep 17 00:00:00 2001 From: Andrei Karas Date: Sun, 2 Jan 2011 01:48:38 +0200 Subject: Initial commit. This code based on mana client http://www.gitorious.org/mana/mana and my private repository. --- src/net/tmwa/playerhandler.cpp | 752 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 752 insertions(+) create mode 100644 src/net/tmwa/playerhandler.cpp (limited to 'src/net/tmwa/playerhandler.cpp') diff --git a/src/net/tmwa/playerhandler.cpp b/src/net/tmwa/playerhandler.cpp new file mode 100644 index 000000000..1d0d3e67d --- /dev/null +++ b/src/net/tmwa/playerhandler.cpp @@ -0,0 +1,752 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * + * This file is part of The Mana Client. + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#include "net/tmwa/playerhandler.h" + +#include "event.h" +#include "game.h" +#include "localplayer.h" +#include "log.h" +#include "party.h" +#include "playerinfo.h" +#include "units.h" + +#include "gui/buy.h" +#include "gui/buysell.h" +#include "gui/gui.h" +#include "gui/npcdialog.h" +#include "gui/okdialog.h" +#include "gui/sell.h" +#include "gui/statuswindow.h" +#include "gui/viewport.h" + +#include "net/messagein.h" +#include "net/messageout.h" + +#include "net/tmwa/protocol.h" +#include "net/tmwa/npchandler.h" + +#include "utils/stringutils.h" +#include "utils/gettext.h" + +extern OkDialog *weightNotice; +extern OkDialog *deathNotice; + +// Max. distance we are willing to scroll after a teleport; +// everything beyond will reset the port hard. +static const int MAP_TELEPORT_SCROLL_DISTANCE = 8; + +// TODO Move somewhere else +namespace +{ + /** + * Listener used for handling the overweigth message. + */ + struct WeightListener : public gcn::ActionListener + { + void action(const gcn::ActionEvent &event _UNUSED_) + { + weightNotice = NULL; + } + } weightListener; + + /** + * Listener used for handling death message. + */ + struct DeathListener : public gcn::ActionListener + { + void action(const gcn::ActionEvent &event _UNUSED_) + { + Net::getPlayerHandler()->respawn(); + deathNotice = NULL; + + BuyDialog::closeAll(); + BuySellDialog::closeAll(); + NpcDialog::closeAll(); + SellDialog::closeAll(); + + if (viewport) + viewport->closePopupMenu(); + + TmwAthena::NpcHandler *handler = + static_cast(Net::getNpcHandler()); + if (handler) + handler->clearDialogs(); + if (player_node) + player_node->respawn(); + } + } deathListener; + +} // anonymous namespace + +static const char *randomDeathMessage() +{ + static char const *const deadMsg[] = + { + N_("You are dead."), + N_("We regret to inform you that your character was killed in " + "battle."), + N_("You are not that alive anymore."), + N_("The cold hands of the grim reaper are grabbing for your soul."), + N_("Game Over!"), + N_("Insert coin to continue."), + N_("No, kids. Your character did not really die. It... " + "err... went to a better place."), + N_("Your plan of breaking your enemies weapon by " + "bashing it with your throat failed."), + N_("I guess this did not run too well."), + // NetHack reference: + N_("Do you want your possessions identified?"), + // Secret of Mana reference: + N_("Sadly, no trace of you was ever found..."), + // Final Fantasy VI reference: + N_("Annihilated."), + // Earthbound reference: + N_("Looks like you got your head handed to you."), + // Leisure Suit Larry 1 reference: + N_("You screwed up again, dump your body down the tubes " + "and get you another one."), + // Monty Python references (Dead Parrot sketch mostly): + N_("You're not dead yet. You're just resting."), + N_("You are no more."), + N_("You have ceased to be."), + N_("You've expired and gone to meet your maker."), + N_("You're a stiff."), + N_("Bereft of life, you rest in peace."), + N_("If you weren't so animated, you'd be pushing up the daisies."), + N_("Your metabolic processes are now history."), + N_("You're off the twig."), + N_("You've kicked the bucket."), + N_("You've shuffled off your mortal coil, run down the " + "curtain and joined the bleedin' choir invisibile."), + N_("You are an ex-player."), + N_("You're pining for the fjords.") + }; + + const int random = rand() % (sizeof(deadMsg) / sizeof(deadMsg[0])); + return gettext(deadMsg[random]); +} + +extern Net::PlayerHandler *playerHandler; + +namespace TmwAthena +{ + +PlayerHandler::PlayerHandler() +{ + static const Uint16 _messages[] = + { + SMSG_WALK_RESPONSE, + SMSG_PLAYER_WARP, + SMSG_PLAYER_STAT_UPDATE_1, + SMSG_PLAYER_STAT_UPDATE_2, + SMSG_PLAYER_STAT_UPDATE_3, + SMSG_PLAYER_STAT_UPDATE_4, + SMSG_PLAYER_STAT_UPDATE_5, + SMSG_PLAYER_STAT_UPDATE_6, + SMSG_PLAYER_ARROW_MESSAGE, + 0 + }; + handledMessages = _messages; + playerHandler = this; +} + +void PlayerHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case SMSG_WALK_RESPONSE: + /* + * This client assumes that all walk messages succeed, + * and that the server will send a correction notice + * otherwise. + */ + Uint16 srcX, srcY, dstX, dstY; + msg.readInt32(); //tick + msg.readCoordinatePair(srcX, srcY, dstX, dstY); + if (player_node) + player_node->setRealPos(dstX, dstY); +// if (debugChatTab) +// debugChatTab->chatLog("move resp: " + toString((int)srcX) + "," + toString((int)srcY) + " " +// + toString((int)dstX) + "," + toString((int)dstY)); + + break; + + case SMSG_PLAYER_WARP: + { + std::string mapPath = msg.readString(16); + int x = msg.readInt16(); + int y = msg.readInt16(); + + logger->log("Warping to %s (%d, %d)", mapPath.c_str(), x, y); + + if (!player_node) + logger->log1("SMSG_PLAYER_WARP player_node null"); + + /* + * We must clear the local player's target *before* the call + * to changeMap, as it deletes all beings. + */ + if (player_node) + player_node->stopAttack(); + + Game *game = Game::instance(); + + const std::string ¤tMapName = game->getCurrentMapName(); + bool sameMap = (currentMapName == mapPath); + + // Switch the actual map, deleting the previous one if necessary + mapPath = mapPath.substr(0, mapPath.rfind(".")); + game->changeMap(mapPath); + + float scrollOffsetX = 0.0f; + float scrollOffsetY = 0.0f; + + if (player_node) + { + /* Scroll if neccessary */ + if (!sameMap + || (abs(x - player_node->getTileX()) + > MAP_TELEPORT_SCROLL_DISTANCE) + || (abs(y - player_node->getTileY()) + > MAP_TELEPORT_SCROLL_DISTANCE)) + { + Map *map = game->getCurrentMap(); + if (map) + { + scrollOffsetX = (x - player_node->getTileX()) + * map->getTileWidth(); + scrollOffsetY = (y - player_node->getTileY()) + * map->getTileHeight(); + } + } + + player_node->setAction(Being::STAND); + player_node->setTileCoords(x, y); + player_node->naviageClean(); +// player_node->updateNavigateList(); + } + + logger->log("Adjust scrolling by %d:%d", (int) scrollOffsetX, + (int) scrollOffsetY); + + if (viewport) + viewport->scrollBy(scrollOffsetX, scrollOffsetY); + } + break; + + case SMSG_PLAYER_STAT_UPDATE_1: + { + int type = msg.readInt16(); + int value = msg.readInt32(); + if (!player_node) + return; + + switch (type) + { + case 0x0000: + player_node->setWalkSpeed(Vector(value, value, 0)); + PlayerInfo::setStatBase(WALK_SPEED, value); + PlayerInfo::setStatMod(WALK_SPEED, 0); + break; + case 0x0004: break; // manner + case 0x0005: + PlayerInfo::setAttribute(HP, value); + if (player_node->isInParty() && Party::getParty(1)) + { + PartyMember *m = Party::getParty(1) + ->getMember(player_node->getId()); + if (m) + { + m->setHp(value); + m->setMaxHp(PlayerInfo::getAttribute(MAX_HP)); + } + } + break; + case 0x0006: + PlayerInfo::setAttribute(MAX_HP, value); + + if (player_node->isInParty() && Party::getParty(1)) + { + PartyMember *m = Party::getParty(1)->getMember( + player_node->getId()); + if (m) + { + m->setHp(PlayerInfo::getAttribute(HP)); + m->setMaxHp(value); + } + } + break; + case 0x0007: + PlayerInfo::setAttribute(MP, value); + break; + case 0x0008: + PlayerInfo::setAttribute(MAX_MP, value); + break; + case 0x0009: + PlayerInfo::setAttribute(CHAR_POINTS, value); + break; + case 0x000b: + PlayerInfo::setAttribute(LEVEL, value); + if (player_node) + { + player_node->setLevel(value); + player_node->updateName(); + } + break; + case 0x000c: + PlayerInfo::setAttribute(SKILL_POINTS, value); + break; + case 0x0018: + if (!weightNotice) + { + const int max + = PlayerInfo::getAttribute(MAX_WEIGHT) / 2; + const int total + = PlayerInfo::getAttribute(TOTAL_WEIGHT); + if (value >= max && total < max) + { + weightNotice = new OkDialog(_("Message"), + _("You are carrying more than " + "half your weight. You are " + "unable to regain health.")); + weightNotice->addActionListener( + &weightListener); + } + else if (value < max && total >= max) + { + weightNotice = new OkDialog(_("Message"), + _("You are carrying less than " + "half your weight. You are " + "can regain health.")); + weightNotice->addActionListener( + &weightListener); + } + } + PlayerInfo::setAttribute(TOTAL_WEIGHT, value); + break; + case 0x0019: + PlayerInfo::setAttribute(MAX_WEIGHT, value); + break; + + case 0x0029: + PlayerInfo::setStatBase(ATK, value); + break; + case 0x002a: + PlayerInfo::setStatMod(ATK, value); + break; + + case 0x002b: + PlayerInfo::setStatBase(MATK, value); + break; + case 0x002c: + PlayerInfo::setStatMod(MATK, value); + break; + + case 0x002d: + PlayerInfo::setStatBase(DEF, value); + break; + case 0x002e: + PlayerInfo::setStatMod(DEF, value); + break; + + case 0x002f: + PlayerInfo::setStatBase(MDEF, value); + break; + case 0x0030: + PlayerInfo::setStatMod(MDEF, value); + break; + + case 0x0031: + PlayerInfo::setStatBase(HIT, value); + break; + + case 0x0032: + PlayerInfo::setStatBase(FLEE, value); + break; + case 0x0033: + PlayerInfo::setStatMod(FLEE, value); + break; + + case 0x0034: + PlayerInfo::setStatBase(CRIT, value); + break; + + case 0x0035: + player_node->setAttackSpeed(value); + PlayerInfo::setStatBase(ATTACK_SPEED, value); + PlayerInfo::setStatMod(ATTACK_SPEED, 0); + break; + + case 0x0037: + PlayerInfo::setStatBase(JOB, value); + break; + + case 500: + player_node->setGMLevel(value); + break; + + default: + logger->log("QQQQ PLAYER_STAT_UPDATE_1 " + + toString(type) + "," + toString(value)); + break; + } + + if (PlayerInfo::getAttribute(HP) == 0 && !deathNotice) + { + deathNotice = new OkDialog(_("Message"), + randomDeathMessage(), + false); + deathNotice->addActionListener(&deathListener); + player_node->setAction(Being::DEAD); + } + } + break; + + case SMSG_PLAYER_STAT_UPDATE_2: + { + int type = msg.readInt16(); + switch (type) + { + case 0x0001: + PlayerInfo::setAttribute(EXP, msg.readInt32()); + break; + case 0x0002: + PlayerInfo::setStatExperience(JOB, msg.readInt32(), + PlayerInfo::getStatExperience(JOB).second); + break; + case 0x0014: + { + int oldMoney = PlayerInfo::getAttribute(MONEY); + int newMoney = msg.readInt32(); + if (newMoney > oldMoney) + { + SERVER_NOTICE(strprintf(_("You picked up %s."), + Units::formatCurrency(newMoney - + oldMoney).c_str())) + } + else if (newMoney < oldMoney) + { + SERVER_NOTICE(strprintf(_("You spent %s."), + Units::formatCurrency(oldMoney - + newMoney).c_str())) + } + + PlayerInfo::setAttribute(MONEY, newMoney); + break; + } + case 0x0016: + PlayerInfo::setAttribute(EXP_NEEDED, msg.readInt32()); + break; + case 0x0017: + PlayerInfo::setStatExperience(JOB, + PlayerInfo::getStatExperience(JOB).first, + msg.readInt32()); + break; + default: + logger->log("QQQQ PLAYER_STAT_UPDATE_2 " + toString(type)); + break; + } + break; + } + case SMSG_PLAYER_STAT_UPDATE_3: // Update a base attribute + { + int type = msg.readInt32(); + int base = msg.readInt32(); + int bonus = msg.readInt32(); + + PlayerInfo::setStatBase(type, base, false); + PlayerInfo::setStatMod(type, bonus); + } + break; + + case SMSG_PLAYER_STAT_UPDATE_4: // Attribute increase ack + { + int type = msg.readInt16(); + int ok = msg.readInt8(); + int value = msg.readInt8(); + + if (ok != 1) + { + int oldValue = PlayerInfo::getStatBase(type); + int points = PlayerInfo::getAttribute(CHAR_POINTS); + points += oldValue - value; + PlayerInfo::setAttribute(CHAR_POINTS, points); + SERVER_NOTICE(_("Cannot raise skill!")) + } + + PlayerInfo::setStatBase(type, value); + } + break; + + // Updates stats and status points + case SMSG_PLAYER_STAT_UPDATE_5: + PlayerInfo::setAttribute(CHAR_POINTS, msg.readInt16()); + { + int val = msg.readInt8(); + PlayerInfo::setStatBase(STR, val); + if (statusWindow) + statusWindow->setPointsNeeded(STR, msg.readInt8()); + else + msg.readInt8(); + + val = msg.readInt8(); + PlayerInfo::setStatBase(AGI, val); + if (statusWindow) + statusWindow->setPointsNeeded(AGI, msg.readInt8()); + else + msg.readInt8(); + + val = msg.readInt8(); + PlayerInfo::setStatBase(VIT, val); + if (statusWindow) + statusWindow->setPointsNeeded(VIT, msg.readInt8()); + else + msg.readInt8(); + + val = msg.readInt8(); + PlayerInfo::setStatBase(INT, val); + if (statusWindow) + statusWindow->setPointsNeeded(INT, msg.readInt8()); + else + msg.readInt8(); + + val = msg.readInt8(); + PlayerInfo::setStatBase(DEX, val); + if (statusWindow) + statusWindow->setPointsNeeded(DEX, msg.readInt8()); + else + msg.readInt8(); + + val = msg.readInt8(); + PlayerInfo::setStatBase(LUK, val); + if (statusWindow) + statusWindow->setPointsNeeded(LUK, msg.readInt8()); + else + msg.readInt8(); + + PlayerInfo::setStatBase(ATK, msg.readInt16(), false); + PlayerInfo::setStatMod(ATK, msg.readInt16()); + + val = msg.readInt16(); + PlayerInfo::setStatBase(MATK, val, false); + + val = msg.readInt16(); + PlayerInfo::setStatMod(MATK, val); + + PlayerInfo::setStatBase(DEF, msg.readInt16(), false); + PlayerInfo::setStatMod(DEF, msg.readInt16()); + + PlayerInfo::setStatBase(MDEF, msg.readInt16(), false); + PlayerInfo::setStatMod(MDEF, msg.readInt16()); + + PlayerInfo::setStatBase(HIT, msg.readInt16()); + + PlayerInfo::setStatBase(FLEE, msg.readInt16(), false); + PlayerInfo::setStatMod(FLEE, msg.readInt16()); + + PlayerInfo::setStatBase(CRIT, msg.readInt16()); + } + + msg.readInt16(); // manner + break; + + case SMSG_PLAYER_STAT_UPDATE_6: + { + int type = msg.readInt16(); + if (statusWindow) + { + switch (type) + { + case 0x0020: + statusWindow->setPointsNeeded(STR, msg.readInt8()); + break; + case 0x0021: + statusWindow->setPointsNeeded(AGI, msg.readInt8()); + break; + case 0x0022: + statusWindow->setPointsNeeded(VIT, msg.readInt8()); + break; + case 0x0023: + statusWindow->setPointsNeeded(INT, msg.readInt8()); + break; + case 0x0024: + statusWindow->setPointsNeeded(DEX, msg.readInt8()); + break; + case 0x0025: + statusWindow->setPointsNeeded(LUK, msg.readInt8()); + break; + default: + logger->log("QQQQ PLAYER_STAT_UPDATE_6 " + + toString(type)); + break; + } + } + break; + } + case SMSG_PLAYER_ARROW_MESSAGE: + { + int type = msg.readInt16(); + + switch (type) + { + case 0: + { + SERVER_NOTICE(_("Equip arrows first.")) + } + break; + case 3: + // arrows equiped + break; + default: + logger->log("QQQQ 0x013b: Unhandled message %i", type); + break; + } + } + break; + + default: + break; + } +} + +void PlayerHandler::attack(int id, bool keep) +{ + MessageOut outMsg(CMSG_PLAYER_ATTACK); + outMsg.writeInt32(id); + if (keep) + outMsg.writeInt8(7); + else + outMsg.writeInt8(0); +} + +void PlayerHandler::stopAttack() +{ + MessageOut outMsg(CMSG_PLAYER_STOP_ATTACK); +} + +void PlayerHandler::emote(int emoteId) +{ + MessageOut outMsg(CMSG_PLAYER_EMOTE); + outMsg.writeInt8(emoteId); +} + +void PlayerHandler::increaseAttribute(int attr) +{ + if (attr >= STR && attr <= LUK) + { + MessageOut outMsg(CMSG_STAT_UPDATE_REQUEST); + outMsg.writeInt16(attr); + outMsg.writeInt8(1); + } +} + +void PlayerHandler::decreaseAttribute(int attr _UNUSED_) +{ + // Supported by eA? +} + +void PlayerHandler::increaseSkill(int skillId) +{ + if (PlayerInfo::getAttribute(SKILL_POINTS) <= 0) + return; + + MessageOut outMsg(CMSG_SKILL_LEVELUP_REQUEST); + outMsg.writeInt16(skillId); +} + +void PlayerHandler::pickUp(FloorItem *floorItem) +{ + if (!floorItem) + return; + + MessageOut outMsg(CMSG_ITEM_PICKUP); + outMsg.writeInt32(floorItem->getId()); +} + +void PlayerHandler::setDirection(char direction) +{ + MessageOut outMsg(CMSG_PLAYER_CHANGE_DIR); + outMsg.writeInt16(0); + outMsg.writeInt8(direction); +} + +void PlayerHandler::setDestination(int x, int y, int direction) +{ + MessageOut outMsg(CMSG_PLAYER_CHANGE_DEST); + outMsg.writeCoordinates(x, y, direction); +} + +void PlayerHandler::changeAction(Being::Action action) +{ + char type; + switch (action) + { + case Being::SIT: type = 2; break; + case Being::STAND: type = 3; break; + default: return; + } + + MessageOut outMsg(CMSG_PLAYER_CHANGE_ACT); + outMsg.writeInt32(0); + outMsg.writeInt8(type); +} + +void PlayerHandler::respawn() +{ + MessageOut outMsg(CMSG_PLAYER_RESTART); + outMsg.writeInt8(0); +} + +void PlayerHandler::ignorePlayer(const std::string &player _UNUSED_, + bool ignore _UNUSED_) +{ + // TODO +} + +void PlayerHandler::ignoreAll(bool ignore _UNUSED_) +{ + // TODO +} + +bool PlayerHandler::canUseMagic() +{ + return PlayerInfo::getStatEffective(MATK) > 0; +} + +bool PlayerHandler::canCorrectAttributes() +{ + return false; +} + +int PlayerHandler::getJobLocation() +{ + return JOB; +} + +Vector PlayerHandler::getDefaultWalkSpeed() +{ + // Return an normalized speed for any side + // as the offset is calculated elsewhere. + return Vector(150, 150, 0); +} + +} // namespace TmwAthena -- cgit v1.2.3-70-g09d2