diff options
author | Andrei Karas <akaras@inbox.ru> | 2012-03-04 16:32:40 +0300 |
---|---|---|
committer | Andrei Karas <akaras@inbox.ru> | 2012-03-04 16:33:19 +0300 |
commit | 8fd450f698f8277eb8348da914d3dcbc11890ea6 (patch) | |
tree | ebaddac087d5e5b29f23a2aa01dd2758c3b8f68a /src/net/manaserv | |
parent | c6d7f7ed3d6d7827b820670faf6c48f0689fd5c2 (diff) | |
parent | f74202392745923f9ce372a6bdcd0a45db6bcd08 (diff) | |
download | manaplus-8fd450f698f8277eb8348da914d3dcbc11890ea6.tar.gz manaplus-8fd450f698f8277eb8348da914d3dcbc11890ea6.tar.bz2 manaplus-8fd450f698f8277eb8348da914d3dcbc11890ea6.tar.xz manaplus-8fd450f698f8277eb8348da914d3dcbc11890ea6.zip |
Merge branch 'master' into stripped
Conflicts:
configure.ac
data/Makefile.am
data/fonts/liberationsans-bold.ttf
data/fonts/liberationsans.ttf
data/fonts/liberationsansmono-bold.ttf
data/fonts/liberationsansmono.ttf
data/themes/redandblack/CMakeLists.txt
data/themes/redandblack/Makefile.am
po/cs.po
po/de.po
po/es.po
po/fi.po
po/fr.po
po/id.po
po/ja.po
po/manaplus.pot
po/nl_BE.po
po/pl.po
po/pt.po
po/pt_BR.po
po/ru.po
po/zh_CN.po
src/guichan/color.cpp
src/guichan/defaultfont.cpp
src/guichan/focushandler.cpp
src/guichan/include/guichan/actionlistener.hpp
src/guichan/include/guichan/deathlistener.hpp
src/guichan/include/guichan/focushandler.hpp
src/guichan/include/guichan/focuslistener.hpp
src/guichan/include/guichan/graphics.hpp
src/guichan/include/guichan/keylistener.hpp
src/guichan/include/guichan/listmodel.hpp
src/guichan/include/guichan/mouselistener.hpp
src/guichan/include/guichan/sdl/sdlpixel.hpp
src/guichan/include/guichan/widget.hpp
src/guichan/include/guichan/widgetlistener.hpp
src/guichan/include/guichan/widgets/listbox.hpp
src/guichan/include/guichan/widgets/slider.hpp
src/guichan/sdl/sdlgraphics.cpp
src/guichan/sdl/sdlimage.cpp
src/guichan/widgets/scrollarea.cpp
src/guichan/widgets/slider.cpp
src/guichan/widgets/tabbedarea.cpp
src/guichan/widgets/textbox.cpp
src/guichan/widgets/window.cpp
src/net/manaserv/attributes.cpp
src/net/manaserv/beinghandler.cpp
src/net/manaserv/charhandler.cpp
src/net/manaserv/gamehandler.h
src/net/manaserv/inventoryhandler.cpp
src/net/manaserv/inventoryhandler.h
src/net/manaserv/itemhandler.cpp
src/net/manaserv/loginhandler.cpp
src/net/manaserv/network.cpp
Diffstat (limited to 'src/net/manaserv')
-rw-r--r-- | src/net/manaserv/attributes.cpp | 412 | ||||
-rw-r--r-- | src/net/manaserv/beinghandler.cpp | 388 | ||||
-rw-r--r-- | src/net/manaserv/charhandler.cpp | 407 | ||||
-rw-r--r-- | src/net/manaserv/gamehandler.h | 72 | ||||
-rw-r--r-- | src/net/manaserv/inventoryhandler.cpp | 220 | ||||
-rw-r--r-- | src/net/manaserv/inventoryhandler.h | 104 | ||||
-rw-r--r-- | src/net/manaserv/itemhandler.cpp | 91 | ||||
-rw-r--r-- | src/net/manaserv/loginhandler.cpp | 499 | ||||
-rw-r--r-- | src/net/manaserv/network.cpp | 177 |
9 files changed, 2370 insertions, 0 deletions
diff --git a/src/net/manaserv/attributes.cpp b/src/net/manaserv/attributes.cpp new file mode 100644 index 000000000..662032e29 --- /dev/null +++ b/src/net/manaserv/attributes.cpp @@ -0,0 +1,412 @@ +/* + * The ManaPlus Client + * Copyright (C) 2010 The Mana Developers + * Copyright (C) 2011-2012 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 <http://www.gnu.org/licenses/>. + */ + +#include "net/manaserv/attributes.h" + +#include "logger.h" +#include "playerinfo.h" + +#include "gui/statuswindow.h" + +#include "resources/itemdb.h" + +#include "utils/gettext.h" +#include "utils/stringutils.h" +#include "utils/xml.h" + +#include <list> +#include <map> + +#define DEFAULT_ATTRIBUTESDB_FILE "attributes.xml" +#define DEFAULT_POINTS 60 +#define DEFAULT_MIN_PTS 1 +#define DEFAULT_MAX_PTS 20 + +namespace ManaServ +{ +namespace Attributes +{ + typedef struct + { + unsigned int id; + std::string name; + std::string description; + /** Whether the attribute value can be modified by the player */ + bool modifiable; + /**< Attribute scope. */ + std::string scope; + /** The playerInfo core Id the attribute is linked with or -1 if not */ + int playerInfoId; + } Attribute; + + /** Map for attributes. */ + typedef std::map<unsigned int, Attribute> AttributeMap; + static AttributeMap attributes; + + /** tags = effects on attributes. */ + typedef std::map< std::string, std::string > TagMap; + static TagMap tags; + + /** List of modifiable attribute names used at character's creation. */ + static std::vector<std::string> attributeLabels; + + /** Characters creation points. */ + static unsigned int creationPoints = 0; + static unsigned int attributeMinimum = 0; + static unsigned int attributeMaximum = 0; + + unsigned int getCreationPoints() + { return creationPoints; } + + unsigned int getAttributeMinimum() + { return attributeMinimum; } + + unsigned int getAttributeMaximum() + { return attributeMaximum; } + + std::vector<std::string>& getLabels() + { return attributeLabels; } + + /** + * Fills the list of base attribute labels. + */ + static void fillLabels() + { + // Fill up the modifiable attribute label list. + attributeLabels.clear(); + AttributeMap::const_iterator it, it_end; + for (it = attributes.begin(), it_end = attributes.end(); + it != it_end; ++it) + { + if (it->second.modifiable && (it->second.scope == "character" + || it->second.scope == "being")) + { + attributeLabels.push_back(it->second.name + ":"); + } + } + } + + /** + * Fills the list of base attribute labels. + */ + static int getPlayerInfoIdFromAttrType(std::string attrType) + { + toLower(attrType); + if (attrType == "level") + return ::LEVEL; + else if (attrType == "hp") + return ::HP; + else if (attrType == "max-hp") + return ::MAX_HP; + else if (attrType == "mp") + return ::MP; + else if (attrType == "max-mp") + return ::MAX_MP; + else if (attrType == "exp") + return ::EXP; + else if (attrType == "exp-needed") + return ::EXP_NEEDED; + else if (attrType == "money") + return ::MONEY; + else if (attrType == "total-weight") + return ::TOTAL_WEIGHT; + else if (attrType == "max-weight") + return ::MAX_WEIGHT; + else if (attrType == "skill-points") + return ::SKILL_POINTS; + else if (attrType == "char-points") + return ::CHAR_POINTS; + else if (attrType == "corr-points") + return ::CORR_POINTS; + else if (attrType == "none") + return -2; // Used to hide the attribute display. + + return -1; // Not linked to a playerinfo stat. + } + + int getPlayerInfoIdFromAttrId(int attrId) + { + AttributeMap::const_iterator it = attributes.find(attrId); + + if (it != attributes.end()) + return it->second.playerInfoId; + + return -1; + } + + static void loadBuiltins() + { + { + Attribute a; + a.id = 16; + a.name = _("Strength"); + a.description = ""; + a.modifiable = true; + a.scope = "character"; + a.playerInfoId = -1; + + attributes[a.id] = a; + tags.insert(std::make_pair("str", _("Strength %+.1f"))); + } + + { + Attribute a; + a.id = 17; + a.name = _("Agility"); + a.description = ""; + a.modifiable = true; + a.scope = "character"; + a.playerInfoId = -1; + + attributes[a.id] = a; + tags.insert(std::make_pair("agi", _("Agility %+.1f"))); + } + + { + Attribute a; + a.id = 18; + a.name = _("Dexterity"); + a.description = ""; + a.modifiable = true; + a.scope = "character"; + a.playerInfoId = -1; + + attributes[a.id] = a; + tags.insert(std::make_pair("dex", _("Dexterity %+.1f"))); + } + + { + Attribute a; + a.id = 19; + a.name = _("Vitality"); + a.description = ""; + a.modifiable = true; + a.scope = "character"; + a.playerInfoId = -1; + + attributes[a.id] = a; + tags.insert(std::make_pair("vit", _("Vitality %+.1f"))); + } + + { + Attribute a; + a.id = 20; + a.name = _("Intelligence"); + a.description = ""; + a.modifiable = true; + a.scope = "character"; + a.playerInfoId = -1; + + attributes[a.id] = a; + tags.insert(std::make_pair("int", _("Intelligence %+.1f"))); + } + + { + Attribute a; + a.id = 21; + a.name = _("Willpower"); + a.description = ""; + a.modifiable = true; + a.scope = "character"; + a.playerInfoId = -1; + + attributes[a.id] = a; + tags.insert(std::make_pair("wil", _("Willpower %+.1f"))); + } + } + + void load() + { + logger->log("Initializing attributes database..."); + + XML::Document doc(DEFAULT_ATTRIBUTESDB_FILE); + XmlNodePtr rootNode = doc.rootNode(); + + if (!rootNode || !xmlNameEqual(rootNode, "attributes")) + { + logger->log("Attributes: Error while loading " + DEFAULT_ATTRIBUTESDB_FILE ". Using Built-ins."); + loadBuiltins(); + fillLabels(); + return; + } + + for_each_xml_child_node(node, rootNode) + { + if (xmlNameEqual(node, "attribute")) + { + int id = XML::getProperty(node, "id", 0); + + if (!id) + { + logger->log("Attributes: Invalid or missing stat ID in " + DEFAULT_ATTRIBUTESDB_FILE "!"); + continue; + } + else if (attributes.find(id) != attributes.end()) + { + logger->log("Attributes: Redefinition of stat ID %d", id); + } + + std::string name = XML::getProperty(node, "name", ""); + + if (name.empty()) + { + logger->log("Attributes: Invalid or missing stat name in " + DEFAULT_ATTRIBUTESDB_FILE "!"); + continue; + } + + // Create the attribute. + Attribute a; + a.id = id; + a.name = name; + a.description = XML::getProperty(node, "desc", ""); + a.modifiable = XML::getBoolProperty(node, "modifiable", false); + a.scope = XML::getProperty(node, "scope", "none"); + a.playerInfoId = getPlayerInfoIdFromAttrType( + XML::getProperty(node, "player-info", "")); + + attributes[id] = a; + + unsigned int count = 0; + for_each_xml_child_node(effectNode, node) + { + if (!xmlNameEqual(effectNode, "modifier")) + continue; + ++count; + std::string tag = XML::getProperty(effectNode, "tag", ""); + if (tag.empty()) + { + if (name.empty()) + { + logger->log("Attribute modifier in attribute" + " %u:%s: Empty name definition " + "on empty tag definition, skipping.", + a.id, a.name.c_str()); + --count; + continue; + } + tag = name.substr(0, name.size() > 3 + ? 3 : name.size()); + tag = toLower(tag) + toString(count); + } + + std::string effect = XML::getProperty( + effectNode, "effect", ""); + + if (effect.empty()) + { + if (name.empty()) + { + logger->log("Attribute modifier in attribute" + " %u:%s: Empty name definition " + "on empty effect definition, skipping.", + a.id, a.name.c_str()); + --count; + continue; + } + else + { + effect = name + " %+f"; + } + } + tags.insert(std::make_pair(tag, effect)); + } + logger->log("Found %d tags for attribute %d.", count, id); + + }// End attribute + else if (xmlNameEqual(node, "points")) + { + creationPoints = XML::getProperty( + node, "start", DEFAULT_POINTS); + attributeMinimum = XML::getProperty( + node, "minimum", DEFAULT_MIN_PTS); + attributeMaximum = XML::getProperty( + node, "maximum", DEFAULT_MAX_PTS); + logger->log("Loaded points: start: %i, min: %i, max: %i.", + creationPoints, attributeMinimum, attributeMaximum); + } + else + { + continue; + } + } + logger->log("Found %d tags for %d attributes.", int(tags.size()), + int(attributes.size())); + + fillLabels(); + + // Sanity checks on starting points + float modifiableAttributeCount = (float) attributeLabels.size(); + float averageValue = 1; + if (modifiableAttributeCount) + averageValue = ((float) creationPoints) / modifiableAttributeCount; + + if (averageValue > attributeMaximum || averageValue < attributeMinimum + || creationPoints < 1) + { + logger->log("Attributes: Character's point values make " + "the character's creation impossible. " + "Switch back to defaults."); + creationPoints = DEFAULT_POINTS; + attributeMinimum = DEFAULT_MIN_PTS; + attributeMaximum = DEFAULT_MAX_PTS; + } + } + + void unload() + { + attributes.clear(); + } + + void informItemDB() + { + std::vector<ItemDB::Stat> dbStats; + + TagMap::const_iterator it, it_end; + for (it = tags.begin(), it_end = tags.end(); it != it_end; ++it) + dbStats.push_back(ItemDB::Stat(it->first, it->second)); + + ItemDB::setStatsList(dbStats); + } + + void informStatusWindow() + { + if (!statusWindow) + return; + + AttributeMap::const_iterator it, it_end; + for (it = attributes.begin(), it_end = attributes.end(); + it != it_end; ++it) + { + if (it->second.playerInfoId == -1 + && (it->second.scope == "character" + || it->second.scope == "being")) + { + statusWindow->addAttribute(it->second.id, it->second.name, + it->second.modifiable, it->second.description); + } + } + } + +} // namespace Attributes +} // namespace ManaServ diff --git a/src/net/manaserv/beinghandler.cpp b/src/net/manaserv/beinghandler.cpp new file mode 100644 index 000000000..9468baaa1 --- /dev/null +++ b/src/net/manaserv/beinghandler.cpp @@ -0,0 +1,388 @@ +/* + * The ManaPlus Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2012 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 <http://www.gnu.org/licenses/>. + */ + +#include "net/manaserv/beinghandler.h" + +#include "actorspritemanager.h" +#include "being.h" +#include "client.h" +#include "game.h" +#include "localplayer.h" +#include "logger.h" +#include "particle.h" + +#include "gui/okdialog.h" + +#include "net/messagein.h" +#include "net/net.h" + +#include "net/manaserv/playerhandler.h" +#include "net/manaserv/protocol.h" + +#include "resources/colordb.h" + +#include "utils/gettext.h" + +extern Net::BeingHandler *beingHandler; + +namespace ManaServ +{ + +BeingHandler::BeingHandler() +{ + static const Uint16 _messages[] = + { + GPMSG_BEING_ATTACK, + GPMSG_BEING_ENTER, + GPMSG_BEING_LEAVE, + GPMSG_BEINGS_MOVE, + GPMSG_BEINGS_DAMAGE, + GPMSG_BEING_ACTION_CHANGE, + GPMSG_BEING_LOOKS_CHANGE, + GPMSG_BEING_DIR_CHANGE, + 0 + }; + handledMessages = _messages; + beingHandler = this; +} + +void BeingHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case GPMSG_BEING_ENTER: + handleBeingEnterMessage(msg); + break; + case GPMSG_BEING_LEAVE: + handleBeingLeaveMessage(msg); + break; + case GPMSG_BEINGS_MOVE: + handleBeingsMoveMessage(msg); + break; + case GPMSG_BEING_ATTACK: + handleBeingAttackMessage(msg); + break; + case GPMSG_BEINGS_DAMAGE: + handleBeingsDamageMessage(msg); + break; + case GPMSG_BEING_ACTION_CHANGE: + handleBeingActionChangeMessage(msg); + break; + case GPMSG_BEING_LOOKS_CHANGE: + handleBeingLooksChangeMessage(msg); + break; + case GPMSG_BEING_DIR_CHANGE: + handleBeingDirChangeMessage(msg); + break; + default: + break; + } +} + +Vector BeingHandler::giveSpeedInPixelsPerTicks(float speedInTilesPerSeconds) +{ + Vector speedInTicks; + Game *game = Game::instance(); + Map *map = 0; + if (game) + { + map = game->getCurrentMap(); + if (map) + { + speedInTicks.x = speedInTilesPerSeconds + * (float)map->getTileWidth() + / 1000 * (float) MILLISECONDS_IN_A_TICK; + speedInTicks.y = speedInTilesPerSeconds + * (float)map->getTileHeight() + / 1000 * (float) MILLISECONDS_IN_A_TICK; + } + } + + if (!game || !map) + { + speedInTicks.x = speedInTicks.y = 0; + logger->log1("Manaserv::BeingHandler: Speed wasn't given back" + " because game/Map not initialized."); + } + // We don't use z for now. + speedInTicks.z = 0; + + return speedInTicks; +} + +static void handleLooks(Being *being, Net::MessageIn &msg) +{ + // Order of sent slots. Has to be in sync with the server code. + static int const nb_slots = 4; + static int const slots[nb_slots] = + { + SPRITE_WEAPON, + SPRITE_HAT, + SPRITE_TOPCLOTHES, + SPRITE_BOTTOMCLOTHES + }; + + int mask = msg.readInt8(); + + if (mask & (1 << 7)) + { + // The equipment has to be cleared first. + for (int i = 0; i < nb_slots; ++i) + being->setSprite(slots[i], 0); + } + + // Fill slots enumerated by the bitmask. + for (int i = 0; i < nb_slots; ++i) + { + if (!(mask & (1 << i))) continue; + int id = msg.readInt16(); + being->setSprite(slots[i], id, "", 1, (slots[i] == SPRITE_WEAPON)); + } +} + +void BeingHandler::handleBeingEnterMessage(Net::MessageIn &msg) +{ + int type = msg.readInt8(); + int id = msg.readInt16(); + Being::Action action = (Being::Action)msg.readInt8(); + int px = msg.readInt16(); + int py = msg.readInt16(); + Being *being; + + switch (type) + { + case OBJECT_CHARACTER: + { + std::string name = msg.readString(); + if (player_node->getName() == name) + { + being = player_node; + being->setId(id); + } + else + { + being = actorSpriteManager->createBeing(id, + ActorSprite::PLAYER, 0); + being->setName(name); + } + int hs = msg.readInt8(), hc = msg.readInt8(); + being->setSprite(SPRITE_HAIR, hs * -1, ColorDB::getHairColor(hc)); + being->setGender(msg.readInt8() == GENDER_MALE ? + GENDER_MALE : GENDER_FEMALE); + handleLooks(being, msg); + } break; + + case OBJECT_MONSTER: + case OBJECT_NPC: + { + int subtype = msg.readInt16(); + being = actorSpriteManager->createBeing(id, type == OBJECT_MONSTER + ? ActorSprite::MONSTER : ActorSprite::NPC, subtype); + std::string name = msg.readString(); + if (name.length() > 0) being->setName(name); + } break; + + default: + return; + } + + being->setPosition(px, py); + being->setDestination(px, py); + being->setAction(action); +} + +void BeingHandler::handleBeingLeaveMessage(Net::MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + if (!being) + return; + + actorSpriteManager->destroy(being); +} + +void BeingHandler::handleBeingsMoveMessage(Net::MessageIn &msg) +{ + while (msg.getUnreadLength()) + { + int id = msg.readInt16(); + int flags = msg.readInt8(); + Being *being = actorSpriteManager->findBeing(id); + int sx = 0; + int sy = 0; + int speed = 0; + + if (flags & MOVING_POSITION) + { + sx = msg.readInt16(); + sy = msg.readInt16(); + speed = msg.readInt8(); + } + if (!being || !(flags & (MOVING_POSITION | MOVING_DESTINATION))) + { + continue; + } + if (speed) + { + /* + * The being's speed is transfered in tiles per second * 10 + * to keep it transferable in a Byte. + * We set it back to tiles per second and in a float. + * Then, we translate it in pixels per ticks, to correspond + * with the Being::logic() function calls + * @see MILLISECONDS_IN_A_TICK + */ + being->setWalkSpeed( + giveSpeedInPixelsPerTicks((float) speed / 10)); + } + + // Ignore messages from the server for the local player + if (being == player_node) + continue; + + if (flags & MOVING_POSITION) + { + being->setDestination(sx, sy); + } + } +} + +void BeingHandler::handleBeingAttackMessage(Net::MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + const int direction = msg.readInt8(); + const int attackType = msg.readInt8(); + + if (!being) + return; + + switch (direction) + { + case DIRECTION_UP: being->setDirection(Being::UP); break; + case DIRECTION_DOWN: being->setDirection(Being::DOWN); break; + case DIRECTION_LEFT: being->setDirection(Being::LEFT); break; + case DIRECTION_RIGHT: being->setDirection(Being::RIGHT); break; + default: break; + } + + being->setAction(Being::ATTACK, attackType); +} + +void BeingHandler::handleBeingsDamageMessage(Net::MessageIn &msg) +{ + while (msg.getUnreadLength()) + { + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + int damage = msg.readInt16(); + if (being) + { + being->takeDamage(0, damage, Being::HIT); + } + } +} + +void BeingHandler::handleBeingActionChangeMessage(Net::MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + Being::Action action = (Being::Action) msg.readInt8(); + if (!being) + return; + + being->setAction(action); + + if (action == Being::DEAD && being == player_node) + { + static char const *const deadMsg[] = + { + _("You are dead."), + _("We regret to inform you that your character was killed in " + "battle."), + _("You are not that alive anymore."), + _("The cold hands of the grim reaper are grabbing for your soul."), + _("Game Over!"), + _("No, kids. Your character did not really die. It... err... " + "went to a better place."), + _("Your plan of breaking your enemies weapon by bashing it with " + "your throat failed."), + _("I guess this did not run too well."), + _("Do you want your possessions identified?"), // Nethack reference + _("Sadly, no trace of you was ever found..."), // Secret of Mana + // reference + _("Annihilated."), // Final Fantasy VI reference + _("Looks like you got your head handed to you."), // Earthbound + // reference + _("You screwed up again, dump your body down the tubes and get " + "you another one.") // Leisure Suit Larry 1 Reference + + }; + std::string message(deadMsg[rand() % 13]); + message.append(std::string(" ") + _("Press OK to respawn.")); + OkDialog *dlg = new OkDialog(_("You Died"), + message, DIALOG_OK, false); + dlg->addActionListener(&(ManaServ::respawnListener)); + } +} + +void BeingHandler::handleBeingLooksChangeMessage(Net::MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + if (!being || being->getType() != ActorSprite::PLAYER) + return; + handleLooks(being, msg); + if (msg.getUnreadLength()) + { + int style = msg.readInt16(); + int color = msg.readInt16(); + being->setSprite(SPRITE_HAIR, style * -1, + ColorDB::getHairColor(color)); + } +} + +void BeingHandler::handleBeingDirChangeMessage(Net::MessageIn &msg) +{ + Being *being = actorSpriteManager->findBeing(msg.readInt16()); + if (!being) + return; + int data = msg.readInt8(); + + // The direction for the player's character is handled on client side. + if (being != player_node) + { + switch (data) + { + case DIRECTION_UP: being->setDirection(Being::UP); break; + case DIRECTION_DOWN: being->setDirection(Being::DOWN); break; + case DIRECTION_LEFT: being->setDirection(Being::LEFT); break; + case DIRECTION_RIGHT: being->setDirection(Being::RIGHT); break; + default: break; + } + } +} + +void BeingHandler::requestNameById(int id A_UNUSED) +{ +} + +void BeingHandler::undress(Being *being A_UNUSED) +{ +} + +} // namespace ManaServ diff --git a/src/net/manaserv/charhandler.cpp b/src/net/manaserv/charhandler.cpp new file mode 100644 index 000000000..7977df909 --- /dev/null +++ b/src/net/manaserv/charhandler.cpp @@ -0,0 +1,407 @@ +/* + * The ManaPlus Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2012 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 <http://www.gnu.org/licenses/>. + */ + +#include "net/manaserv/charhandler.h" + +#include "client.h" +#include "localplayer.h" +#include "logger.h" + +#include "gui/charcreatedialog.h" +#include "gui/okdialog.h" + +#include "net/logindata.h" +#include "net/loginhandler.h" +#include "net/net.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/gamehandler.h" +#include "net/manaserv/messagein.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" +#include "net/manaserv/attributes.h" + +#include "resources/colordb.h" + +#include "utils/dtor.h" +#include "utils/gettext.h" + +extern Net::CharHandler *charHandler; +extern ManaServ::GameHandler *gameHandler; + +namespace ManaServ +{ + +extern Connection *accountServerConnection; +extern Connection *gameServerConnection; +extern Connection *chatServerConnection; +extern std::string netToken; +extern ServerInfo gameServer; +extern ServerInfo chatServer; + +CharHandler::CharHandler() +{ + static const Uint16 _messages[] = + { + APMSG_CHAR_CREATE_RESPONSE, + APMSG_CHAR_DELETE_RESPONSE, + APMSG_CHAR_INFO, + APMSG_CHAR_SELECT_RESPONSE, + 0 + }; + handledMessages = _messages; + charHandler = this; +} + +CharHandler::~CharHandler() +{ + clear(); +} + +void CharHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case APMSG_CHAR_CREATE_RESPONSE: + handleCharacterCreateResponse(msg); + break; + + case APMSG_CHAR_DELETE_RESPONSE: + handleCharacterDeleteResponse(msg); + break; + + case APMSG_CHAR_INFO: + handleCharacterInfo(msg); + break; + + case APMSG_CHAR_SELECT_RESPONSE: + handleCharacterSelectResponse(msg); + break; + + default: + break; + } +} + +void CharHandler::handleCharacterInfo(Net::MessageIn &msg) +{ + CachedCharacterInfo info; + info.slot = msg.readInt8(); + info.name = msg.readString(); + info.gender = msg.readInt8() == GENDER_MALE ? GENDER_MALE : + GENDER_FEMALE; + info.hairStyle = msg.readInt8(); + info.hairColor = msg.readInt8(); + info.level = msg.readInt16(); + info.characterPoints = msg.readInt16(); + info.correctionPoints = msg.readInt16(); + + + while (msg.getUnreadLength() > 0) + { + int id = msg.readInt32(); + CachedAttrbiute attr; + attr.base = msg.readInt32() / 256.0; + attr.mod = msg.readInt32() / 256.0; + + info.attribute[id] = attr; + } + + mCachedCharacterInfos.push_back(info); + + updateCharacters(); +} + +void CharHandler::handleCharacterCreateResponse(Net::MessageIn &msg) +{ + const int errMsg = msg.readInt8(); + + if (errMsg != ERRMSG_OK) + { + // Character creation failed + std::string errorMessage(""); + switch (errMsg) + { + case ERRMSG_NO_LOGIN: + errorMessage = _("Not logged in."); + break; + case CREATE_TOO_MUCH_CHARACTERS: + errorMessage = _("No empty slot."); + break; + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("Invalid name."); + break; + case CREATE_EXISTS_NAME: + errorMessage = _("Character's name already exists."); + break; + case CREATE_INVALID_HAIRSTYLE: + errorMessage = _("Invalid hairstyle."); + break; + case CREATE_INVALID_HAIRCOLOR: + errorMessage = _("Invalid hair color."); + break; + case CREATE_INVALID_GENDER: + errorMessage = _("Invalid gender."); + break; + case CREATE_ATTRIBUTES_TOO_HIGH: + errorMessage = _("Character's stats are too high."); + break; + case CREATE_ATTRIBUTES_TOO_LOW: + errorMessage = _("Character's stats are too low."); + break; + case CREATE_ATTRIBUTES_OUT_OF_RANGE: + errorMessage = strprintf( _("At least one stat " + "is out of the permitted range: (%u - %u)."), + Attributes::getAttributeMinimum(), + Attributes::getAttributeMaximum()); + break; + case CREATE_INVALID_SLOT: + errorMessage = _("Invalid slot number."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + new OkDialog(_("Error"), errorMessage, DIALOG_ERROR); + + if (mCharCreateDialog) + mCharCreateDialog->unlock(); + } + else + { + // Close the character create dialog + if (mCharCreateDialog) + { + mCharCreateDialog->scheduleDelete(); + mCharCreateDialog = 0; + } + } +} + +void CharHandler::handleCharacterDeleteResponse(Net::MessageIn &msg) +{ + int errMsg = msg.readInt8(); + if (errMsg == ERRMSG_OK) + { + // Character deletion successful + delete mSelectedCharacter; + mCharacters.remove(mSelectedCharacter); +// mSelectedCharacter = 0; + updateCharSelectDialog(); + new OkDialog(_("Info"), _("Player deleted.")); + } + else + { + // Character deletion failed + std::string errorMessage(""); + switch (errMsg) + { + case ERRMSG_NO_LOGIN: + errorMessage = _("Not logged in."); + break; + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("Selection out of range."); + break; + default: + errorMessage = strprintf(_("Unknown error (%d)."), errMsg); + } + new OkDialog(_("Error"), errorMessage, DIALOG_ERROR); + } + mSelectedCharacter = 0; + unlockCharSelectDialog(); +} + +void CharHandler::handleCharacterSelectResponse(Net::MessageIn &msg) +{ + int errMsg = msg.readInt8(); + + if (errMsg == ERRMSG_OK) + { + netToken = msg.readString(32); + + gameServer.hostname.assign(msg.readString()); + gameServer.port = msg.readInt16(); + + chatServer.hostname.assign(msg.readString()); + chatServer.port = msg.readInt16(); + + logger->log("Game server: %s:%d", gameServer.hostname.c_str(), + gameServer.port); + logger->log("Chat server: %s:%d", chatServer.hostname.c_str(), + chatServer.port); + + // Prevent the selected local player from being deleted + player_node = mSelectedCharacter->dummy; + PlayerInfo::setBackend(mSelectedCharacter->data); + mSelectedCharacter->dummy = 0; + + Client::setState(STATE_CONNECT_GAME); + } + else if (errMsg == ERRMSG_FAILURE) + { + errorMessage = _("No gameservers are available."); + delete_all(mCharacters); + mCharacters.clear(); + Client::setState(STATE_ERROR); + } +} + +void CharHandler::setCharSelectDialog(CharSelectDialog *window) +{ + mCharSelectDialog = window; + updateCharacters(); +} + +void CharHandler::setCharCreateDialog(CharCreateDialog *window) +{ + mCharCreateDialog = window; + + if (!mCharCreateDialog) + return; + + mCharCreateDialog->setAttributes(Attributes::getLabels(), + Attributes::getCreationPoints(), + Attributes::getAttributeMinimum(), + Attributes::getAttributeMaximum()); +} + +void CharHandler::requestCharacters() +{ + if (!accountServerConnection->isConnected()) + { + Net::getLoginHandler()->connect(); + } + else + { + // The characters are already there, continue to character selection + Client::setState(STATE_CHAR_SELECT); + } +} + +void CharHandler::chooseCharacter(Net::Character *character) +{ + mSelectedCharacter = character; + + MessageOut msg(PAMSG_CHAR_SELECT); + msg.writeInt8(mSelectedCharacter->slot); + accountServerConnection->send(msg); +} + +void CharHandler::newCharacter(const std::string &name, + int slot, + bool gender, + int hairstyle, + int hairColor, unsigned char race, + const std::vector<int> &stats) +{ + MessageOut msg(PAMSG_CHAR_CREATE); + + msg.writeString(name); + msg.writeInt8(hairstyle); + msg.writeInt8(hairColor); + msg.writeInt8(gender); + msg.writeInt8(slot); + + std::vector<int>::const_iterator it, it_end; + for (it = stats.begin(), it_end = stats.end(); it != it_end; ++it) + msg.writeInt16((*it)); + + accountServerConnection->send(msg); +} + +void CharHandler::deleteCharacter(Net::Character *character) +{ + mSelectedCharacter = character; + + MessageOut msg(PAMSG_CHAR_DELETE); + msg.writeInt8(mSelectedCharacter->slot); + accountServerConnection->send(msg); +} + +void CharHandler::switchCharacter() +{ + gameHandler->quit(true); +} + +unsigned int CharHandler::baseSprite() const +{ + return SPRITE_BASE; +} + +unsigned int CharHandler::hairSprite() const +{ + return SPRITE_HAIR; +} + +unsigned int CharHandler::maxSprite() const +{ + return SPRITE_VECTOREND; +} + +void CharHandler::updateCharacters() +{ + // Delete previous characters + delete_all(mCharacters); + mCharacters.clear(); + + if (!mCharSelectDialog) + return; + + // Create new characters and initialize them from the cached infos + for (unsigned i = 0; i < mCachedCharacterInfos.size(); ++i) + { + const CachedCharacterInfo &info = mCachedCharacterInfos.at(i); + + Net::Character *character = new Net::Character; + character->slot = info.slot; + LocalPlayer *player = character->dummy = new LocalPlayer; + player->setName(info.name); + player->setGender(info.gender); + player->setSprite(SPRITE_HAIR, info.hairStyle * -1, + ColorDB::getHairColor(info.hairColor)); + character->data.mAttributes[LEVEL] = info.level; + character->data.mAttributes[CHAR_POINTS] = info.characterPoints; + character->data.mAttributes[CORR_POINTS] = info.correctionPoints; + + for (CachedAttributes::const_iterator it = info.attribute.begin(), + it_end = info.attribute.end(); it != it_end; ++it) + { + character->data.mStats[i].base = it->second.base; + character->data.mStats[i].mod = it->second.mod; + } + + mCharacters.push_back(character); + } + + updateCharSelectDialog(); +} + +void CharHandler::clear() +{ + setCharCreateDialog(0); + setCharSelectDialog(0); + + mCachedCharacterInfos.clear(); + updateCharacters(); +} + +} // namespace ManaServ diff --git a/src/net/manaserv/gamehandler.h b/src/net/manaserv/gamehandler.h new file mode 100644 index 000000000..2019755ea --- /dev/null +++ b/src/net/manaserv/gamehandler.h @@ -0,0 +1,72 @@ +/* + * The ManaPlus Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2012 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef NET_MANASERV_MAPHANDLER_H +#define NET_MANASERV_MAPHANDLER_H + +#include "net/gamehandler.h" +#include "net/serverinfo.h" + +#include "net/manaserv/messagehandler.h" + +namespace ManaServ +{ + +class GameHandler : public MessageHandler, public Net::GameHandler +{ + public: + GameHandler(); + + void handleMessage(Net::MessageIn &msg); + + void connect(); + + bool isConnected(); + + void disconnect(); + + void who(); + + void quit(bool reconnectAccount); + + void quit() + { quit(false); } + + void ping(int tick); + + bool removeDeadBeings() const + { return false; } + + void clear(); + + void gameLoading(); + + /** The ManaServ protocol doesn't use the MP status bar. */ + bool canUseMagicBar() const + { return false; } + + void disconnect2(); +}; + +} // namespace ManaServ + +#endif // NET_MANASERV_MAPHANDLER_H diff --git a/src/net/manaserv/inventoryhandler.cpp b/src/net/manaserv/inventoryhandler.cpp new file mode 100644 index 000000000..2c9c84c76 --- /dev/null +++ b/src/net/manaserv/inventoryhandler.cpp @@ -0,0 +1,220 @@ +/* + * The ManaPlus Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2012 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 <http://www.gnu.org/licenses/>. + */ + +#include "net/manaserv/inventoryhandler.h" + +#include "equipment.h" +#include "inventory.h" +#include "item.h" +#include "itemshortcut.h" +#include "localplayer.h" +#include "playerinfo.h" + +#include "gui/chatwindow.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/messagein.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" + +#include "resources/iteminfo.h" + +#include "logger.h" // <<< REMOVE ME! + +extern Net::InventoryHandler *inventoryHandler; + +namespace ManaServ +{ + +extern Connection *gameServerConnection; + +InventoryHandler::InventoryHandler() +{ + static const Uint16 _messages[] = + { + GPMSG_INVENTORY_FULL, + GPMSG_INVENTORY, + GPMSG_EQUIP, + 0 + }; + handledMessages = _messages; + inventoryHandler = this; +} + +void InventoryHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case GPMSG_INVENTORY_FULL: + { + PlayerInfo::clearInventory(); + PlayerInfo::getEquipment()->setBackend(&mEquips); + int count = msg.readInt16(); + while (count--) + { + unsigned int slot = msg.readInt16(); + int id = msg.readInt16(); + unsigned int amount = msg.readInt16(); + PlayerInfo::setInventoryItem(slot, id, amount, 0); + } + while (msg.getUnreadLength()) + { + unsigned int slot = msg.readInt8(); + unsigned int ref = msg.readInt16(); + + mEquips.addEquipment(slot, ref); + } + } + break; + + case GPMSG_INVENTORY: + while (msg.getUnreadLength()) + { + unsigned int slot = msg.readInt16(); + int id = msg.readInt16(); + unsigned int amount = id ? msg.readInt16() : 0; + PlayerInfo::setInventoryItem(slot, id, amount, 0); + } + break; + + case GPMSG_EQUIP: + while (msg.getUnreadLength()) + { + unsigned int ref = msg.readInt16(); + int count = msg.readInt8(); + while (count--) + { + unsigned int slot = msg.readInt8(); + unsigned int used = msg.readInt8(); + + mEquips.setEquipment(slot, used, ref); + } + } + break; + default: + break; + } +} + +void InventoryHandler::equipItem(const Item *item) +{ + MessageOut msg(PGMSG_EQUIP); + msg.writeInt8(item->getInvIndex()); + gameServerConnection->send(msg); +} + +void InventoryHandler::unequipItem(const Item *item) +{ + MessageOut msg(PGMSG_UNEQUIP); + msg.writeInt8(item->getInvIndex()); + gameServerConnection->send(msg); + +/* + // Tidy equipment directly to avoid weapon still shown bug, for instance + int equipSlot = item->getInvIndex(); + logger->log("Unequipping %d", equipSlot); + mEquips.setEquipment(equipSlot, 0); +*/ +} + +void InventoryHandler::useItem(const Item *item) +{ + MessageOut msg(PGMSG_USE_ITEM); + msg.writeInt8(item->getInvIndex()); + gameServerConnection->send(msg); +} + +void InventoryHandler::dropItem(const Item *item, int amount) +{ + MessageOut msg(PGMSG_DROP); + msg.writeInt8(item->getInvIndex()); + msg.writeInt8(amount); + gameServerConnection->send(msg); +} + +bool InventoryHandler::canSplit(const Item *item) const +{ + return item && !item->isEquipment() && item->getQuantity() > 1; +} + +void InventoryHandler::splitItem(const Item *item, int amount) +{ + int newIndex = PlayerInfo::getInventory()->getFreeSlot(); + if (newIndex > Inventory::NO_SLOT_INDEX) + { + MessageOut msg(PGMSG_MOVE_ITEM); + msg.writeInt8(item->getInvIndex()); + msg.writeInt8(newIndex); + msg.writeInt8(amount); + gameServerConnection->send(msg); + } +} + +void InventoryHandler::moveItem(int oldIndex, int newIndex) +{ + if (oldIndex == newIndex) + return; + + MessageOut msg(PGMSG_MOVE_ITEM); + msg.writeInt8(oldIndex); + msg.writeInt8(newIndex); + msg.writeInt8(PlayerInfo::getInventory()->getItem(oldIndex) + ->getQuantity()); + gameServerConnection->send(msg); +} + +void InventoryHandler::openStorage(int type A_UNUSED) +{ + // TODO +} + +void InventoryHandler::closeStorage(int type A_UNUSED) +{ + // TODO +} + +void InventoryHandler::moveItem2(int source A_UNUSED, int slot A_UNUSED, + int amount A_UNUSED, int destination A_UNUSED) +{ + // TODO +} + +size_t InventoryHandler::getSize(int type) const +{ + switch (type) + { + case Inventory::INVENTORY: + case Inventory::TRADE: + return 50; + case Inventory::STORAGE: + return 300; + default: + return 0; + } +} + +int InventoryHandler::convertFromServerSlot(int eAthenaSlot) const +{ + return eAthenaSlot; +} + +} // namespace ManaServ diff --git a/src/net/manaserv/inventoryhandler.h b/src/net/manaserv/inventoryhandler.h new file mode 100644 index 000000000..af340399e --- /dev/null +++ b/src/net/manaserv/inventoryhandler.h @@ -0,0 +1,104 @@ +/* + * The ManaPlus Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2012 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef NET_MANASERV_INVENTORYHANDLER_H +#define NET_MANASERV_INVENTORYHANDLER_H + +#include "equipment.h" + +#include "net/inventoryhandler.h" + +#include "net/manaserv/messagehandler.h" + +namespace ManaServ +{ + +class EquipBackend : public Equipment::Backend +{ + public: + EquipBackend() + { memset(mEquipment, 0, sizeof(mEquipment)); } + + Item *getEquipment(int index) const + { return mEquipment[index]; } + + void clear() + { + for (int i = 0; i < EQUIPMENT_SIZE; ++i) + delete mEquipment[i]; + + std::fill_n(mEquipment, EQUIPMENT_SIZE, (Item*) 0); + } + + void setEquipment(unsigned int slot, unsigned int used, int reference) + { + printf("Equip: %d at %dx%d\n", reference, slot, used); + } + + void addEquipment(unsigned int slot, int reference) + { + printf("Equip: %d at %d\n", reference, slot); + } + + private: + Item *mEquipment[EQUIPMENT_SIZE]; +}; + +class InventoryHandler : public MessageHandler, Net::InventoryHandler +{ + public: + InventoryHandler(); + + void handleMessage(Net::MessageIn &msg); + + void equipItem(const Item *item); + + void unequipItem(const Item *item); + + void useItem(const Item *item); + + void dropItem(const Item *item, int amount); + + bool canSplit(const Item *item) const; + + void splitItem(const Item *item, int amount); + + void moveItem(int oldIndex, int newIndex); + + void openStorage(int type); + + void closeStorage(int type); + + void moveItem2(int source, int slot, int amount, + int destination); + + size_t getSize(int type) const; + + int convertFromServerSlot(int eAthenaSlot) const; + + private: + EquipBackend mEquips; +}; + +} // namespace ManaServ + +#endif // NET_MANASERV_INVENTORYHANDLER_H diff --git a/src/net/manaserv/itemhandler.cpp b/src/net/manaserv/itemhandler.cpp new file mode 100644 index 000000000..8ebe965ec --- /dev/null +++ b/src/net/manaserv/itemhandler.cpp @@ -0,0 +1,91 @@ +/* + * The ManaPlus Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2012 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 <http://www.gnu.org/licenses/>. + */ + +#include "net/manaserv/itemhandler.h" + +#include "actorspritemanager.h" + +#include "net/manaserv/protocol.h" +#include "net/manaserv/messagein.h" + +#include "game.h" +#include "map.h" +#include "logger.h" + +namespace ManaServ +{ + +ItemHandler::ItemHandler() +{ + static const Uint16 _messages[] = + { + GPMSG_ITEMS, + GPMSG_ITEM_APPEAR, + 0 + }; + handledMessages = _messages; +} + +void ItemHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case GPMSG_ITEM_APPEAR: + case GPMSG_ITEMS: + { + while (msg.getUnreadLength()) + { + int itemId = msg.readInt16(); + int x = msg.readInt16(); + int y = msg.readInt16(); + int id = (x << 16) | y; // dummy id + + if (itemId) + { + if (Game *game = Game::instance()) + { + if (Map *map = game->getCurrentMap()) + { + actorSpriteManager->createItem(id, itemId, + x / map->getTileWidth(), + y / map->getTileHeight(), + 0, 1, 0, 0); + } + else + { + logger->log( + "ItemHandler: An item wasn't created " + "because of Game/Map not initialized..."); + } + } + } + else if (FloorItem *item = actorSpriteManager->findItem(id)) + { + actorSpriteManager->destroy(item); + } + } + } break; + default: break; + } +} + +} // namespace ManaServ diff --git a/src/net/manaserv/loginhandler.cpp b/src/net/manaserv/loginhandler.cpp new file mode 100644 index 000000000..f93d8e44c --- /dev/null +++ b/src/net/manaserv/loginhandler.cpp @@ -0,0 +1,499 @@ +/* + * The ManaPlus Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2012 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 <http://www.gnu.org/licenses/>. + */ + +#include "net/manaserv/loginhandler.h" + +#include "client.h" +#include "logger.h" + +#include "net/logindata.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/messagein.h" +#include "net/manaserv/messageout.h" +#include "net/manaserv/protocol.h" + +#include "utils/gettext.h" +#include "utils/paths.h" +#include "utils/sha256.h" + +extern Net::LoginHandler *loginHandler; + +namespace ManaServ +{ + +extern Connection *accountServerConnection; +extern std::string netToken; + +LoginHandler::LoginHandler() +{ + static const Uint16 _messages[] = + { + APMSG_LOGIN_RESPONSE, + APMSG_REGISTER_RESPONSE, + APMSG_RECONNECT_RESPONSE, + APMSG_PASSWORD_CHANGE_RESPONSE, + APMSG_EMAIL_CHANGE_RESPONSE, + APMSG_LOGOUT_RESPONSE, + APMSG_UNREGISTER_RESPONSE, + APMSG_REGISTER_INFO_RESPONSE, + 0 + }; + handledMessages = _messages; + loginHandler = this; + mMinUserNameLength = 4; + mMaxUserNameLength = 10; + mLoginData = 0; +} + +void LoginHandler::handleMessage(Net::MessageIn &msg) +{ + switch (msg.getId()) + { + case APMSG_LOGIN_RESPONSE: + handleLoginResponse(msg); + break; + + case APMSG_REGISTER_RESPONSE: + handleRegisterResponse(msg); + break; + + case APMSG_RECONNECT_RESPONSE: + { + int errMsg = msg.readInt8(); + // Successful login + if (errMsg == ERRMSG_OK) + { + Client::setState(STATE_CHAR_SELECT); + } + // Login failed + else + { + switch (errMsg) + { + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("Wrong magic_token."); + break; + case ERRMSG_FAILURE: + errorMessage = _("Already logged in."); + break; + case LOGIN_BANNED: + errorMessage = _("Account banned."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + Client::setState(STATE_ERROR); + } + } + break; + + case APMSG_PASSWORD_CHANGE_RESPONSE: + { + int errMsg = msg.readInt8(); + // Successful pass change + if (errMsg == ERRMSG_OK) + { + Client::setState(STATE_CHANGEPASSWORD_SUCCESS); + } + // pass change failed + else + { + switch (errMsg) + { + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("New password incorrect."); + break; + case ERRMSG_FAILURE: + errorMessage = _("Old password incorrect."); + break; + case ERRMSG_NO_LOGIN: + errorMessage = + _("Account not connected. Please login first."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + Client::setState(STATE_ACCOUNTCHANGE_ERROR); + } + } + break; + + case APMSG_EMAIL_CHANGE_RESPONSE: + { + int errMsg = msg.readInt8(); + // Successful pass change + if (errMsg == ERRMSG_OK) + { + Client::setState(STATE_CHANGEEMAIL_SUCCESS); + } + // pass change failed + else + { + switch (errMsg) + { + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("New email address incorrect."); + break; + case ERRMSG_FAILURE: + errorMessage = _("Old email address incorrect."); + break; + case ERRMSG_NO_LOGIN: + errorMessage = + _("Account not connected. Please login first."); + break; + case ERRMSG_EMAIL_ALREADY_EXISTS: + errorMessage = + _("The new email address already exists."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + Client::setState(STATE_ACCOUNTCHANGE_ERROR); + } + } + break; + case APMSG_LOGOUT_RESPONSE: + { + int errMsg = msg.readInt8(); + + // Successful logout + if (errMsg == ERRMSG_OK) + { + // TODO: handle logout + } + // Logout failed + else + { + switch (errMsg) + { + case ERRMSG_NO_LOGIN: + errorMessage = "Accountserver: Not logged in"; + break; + default: + errorMessage = "Accountserver: Unknown error"; + break; + } + Client::setState(STATE_ERROR); + } + } + break; + case APMSG_UNREGISTER_RESPONSE: + { + int errMsg = msg.readInt8(); + // Successful unregistration + if (errMsg == ERRMSG_OK) + { + Client::setState(STATE_UNREGISTER); + } + // Unregistration failed + else + { + switch (errMsg) + { + case ERRMSG_INVALID_ARGUMENT: + errorMessage = + "Accountserver: Wrong username or password"; + break; + default: + errorMessage = "Accountserver: Unknown error"; + break; + } + Client::setState(STATE_ACCOUNTCHANGE_ERROR); + } + } + break; + + case APMSG_REGISTER_INFO_RESPONSE: + { + int allowed = msg.readInt8(); + + if (allowed) + { + mMinUserNameLength = msg.readInt8(); + mMaxUserNameLength = msg.readInt8(); + std::string captchaURL = msg.readString(); + std::string captchaInstructions = msg.readString(); + + printf("%s: %s\n", captchaURL.c_str(), + captchaInstructions.c_str()); + + Client::setState(STATE_REGISTER); + } + else + { + errorMessage = msg.readString(); + + if (errorMessage.empty()) + errorMessage = _("Client registration is not allowed. " + "Please contact server administration."); + Client::setState(STATE_LOGIN_ERROR); + } + } + break; + default: + break; + } +} + +void LoginHandler::handleLoginResponse(Net::MessageIn &msg) +{ + const int errMsg = msg.readInt8(); + + if (errMsg == ERRMSG_OK) + { + readServerInfo(msg); + // No worlds atm, but future use :-D + Client::setState(STATE_WORLD_SELECT); + } + else + { + switch (errMsg) + { + case LOGIN_INVALID_VERSION: + errorMessage = _("Client version is too old."); + break; + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("Wrong username or password."); + break; + case ERRMSG_FAILURE: + errorMessage = _("Already logged in."); + break; + case LOGIN_BANNED: + errorMessage = _("Account banned"); + break; + case LOGIN_INVALID_TIME: + errorMessage = _("Login attempt too soon after previous " + "attempt."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + Client::setState(STATE_LOGIN_ERROR); + } +} + +void LoginHandler::handleRegisterResponse(Net::MessageIn &msg) +{ + const int errMsg = msg.readInt8(); + + if (errMsg == ERRMSG_OK) + { + readServerInfo(msg); + Client::setState(STATE_WORLD_SELECT); + } + else + { + switch (errMsg) + { + case REGISTER_INVALID_VERSION: + errorMessage = _("Client version is too old."); + break; + case ERRMSG_INVALID_ARGUMENT: + errorMessage = _("Wrong username, password or email address."); + break; + case REGISTER_EXISTS_USERNAME: + errorMessage = _("Username already exists."); + break; + case REGISTER_EXISTS_EMAIL: + errorMessage = _("Email address already exists."); + break; + case REGISTER_CAPTCHA_WRONG: + errorMessage = _("You took too long with the captcha or your " + "response was incorrect."); + break; + default: + errorMessage = _("Unknown error."); + break; + } + Client::setState(STATE_LOGIN_ERROR); + } +} + +void LoginHandler::readServerInfo(Net::MessageIn &msg) +{ + // Safety check for outdated manaserv versions (remove me later) + if (msg.getUnreadLength() == 0) + return; + + // Set the update host when included in the message + std::string updateHost = msg.readString(); + if (!updateHost.empty()) + { + if (!checkPath(updateHost)) + { + logger->log1("Warning: incorrect update server name"); + updateHost = ""; + } + mLoginData->updateHost = updateHost; + } + else + { + logger->log1("Warning: server does not have an update host set!"); + } + + // Read the client data folder for dynamic data loading. + // This is only used by the QT client. + msg.readString(); + + // Read the number of character slots + mLoginData->characterSlots = msg.readInt8(); +} + +void LoginHandler::connect() +{ + accountServerConnection->connect(mServer.hostname, mServer.port); +} + +bool LoginHandler::isConnected() +{ + return accountServerConnection->isConnected(); +} + +void LoginHandler::disconnect() +{ + accountServerConnection->disconnect(); + + if (Client::getState() == STATE_CONNECT_GAME) + { + Client::setState(STATE_GAME); + } +} + +bool LoginHandler::isRegistrationEnabled() +{ + return true; +} + +void LoginHandler::getRegistrationDetails() +{ + MessageOut msg(PAMSG_REQUEST_REGISTER_INFO); + accountServerConnection->send(msg); +} + +unsigned int LoginHandler::getMinUserNameLength() const +{ + return mMinUserNameLength; +} + +unsigned int LoginHandler::getMaxUserNameLength() const +{ + return mMaxUserNameLength; +} + +void LoginHandler::loginAccount(LoginData *loginData) +{ + mLoginData = loginData; + + MessageOut msg(PAMSG_LOGIN); + + msg.writeInt32(0); // client version + msg.writeString(loginData->username); + msg.writeString(sha256(loginData->username + loginData->password)); + + accountServerConnection->send(msg); +} + +void LoginHandler::logout() +{ + MessageOut msg(PAMSG_LOGOUT); + accountServerConnection->send(msg); +} + +void LoginHandler::changeEmail(const std::string &email) +{ + MessageOut msg(PAMSG_EMAIL_CHANGE); + + // Email is sent clearly so the server can validate the data. + // Encryption is assumed server-side. + msg.writeString(email); + + accountServerConnection->send(msg); +} + +void LoginHandler::changePassword(const std::string &username, + const std::string &oldPassword, + const std::string &newPassword) +{ + MessageOut msg(PAMSG_PASSWORD_CHANGE); + + // Change password using SHA2 encryption + msg.writeString(sha256(username + oldPassword)); + msg.writeString(sha256(username + newPassword)); + + accountServerConnection->send(msg); +} + +void LoginHandler::chooseServer(unsigned int server A_UNUSED) +{ + // TODO +} + +void LoginHandler::registerAccount(LoginData *loginData) +{ + mLoginData = loginData; + + MessageOut msg(PAMSG_REGISTER); + + msg.writeInt32(0); // client version + msg.writeString(loginData->username); + // Use a hashed password for privacy reasons + msg.writeString(sha256(loginData->username + loginData->password)); + msg.writeString(loginData->email); + msg.writeString(loginData->captchaResponse); + + accountServerConnection->send(msg); +} + +void LoginHandler::unregisterAccount(const std::string &username, + const std::string &password) +{ + MessageOut msg(PAMSG_UNREGISTER); + + msg.writeString(username); + msg.writeString(sha256(username + password)); + + accountServerConnection->send(msg); +} + +Worlds LoginHandler::getWorlds() const +{ + return Worlds(); +} + +void LoginHandler::reconnect() +{ + MessageOut msg(PAMSG_RECONNECT); + msg.writeString(netToken, 32); + accountServerConnection->send(msg); +} + +void LoginHandler::clearWorlds() +{ + +} + +} // namespace ManaServ diff --git a/src/net/manaserv/network.cpp b/src/net/manaserv/network.cpp new file mode 100644 index 000000000..05041c56a --- /dev/null +++ b/src/net/manaserv/network.cpp @@ -0,0 +1,177 @@ +/* + * The ManaPlus Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2011-2012 The ManaPlus Developers + * + * This file is part of The ManaPlus 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 <http://www.gnu.org/licenses/>. + */ + +#include "net/manaserv/network.h" + +#include "logger.h" + +#include "net/manaserv/connection.h" +#include "net/manaserv/internal.h" +#include "net/manaserv/messagehandler.h" +#include "net/manaserv/messagein.h" + +#include "enet/enet.h" + +#include <map> + +#include "debug.h" + +/** + * The local host which is shared for all outgoing connections. + */ +namespace +{ + ENetHost *client; +} + +namespace ManaServ +{ + +typedef std::map<unsigned short, MessageHandler*> MessageHandlers; +typedef MessageHandlers::const_iterator MessageHandlerIterator; +static MessageHandlers mMessageHandlers; + +void initialize() +{ + if (enet_initialize()) + { + logger->safeError("Failed to initialize ENet."); + } + +#if defined(ENET_VERSION) && ENET_VERSION >= ENET_CUTOFF + client = enet_host_create(nullptr, 3, 0, 0, 0); +#else + client = enet_host_create(nullptr, 3, 0, 0); +#endif + + if (!client) + { + logger->safeError("Failed to create the local host."); + } +} + +void finalize() +{ + if (!client) + return; // Wasn't initialized at all + + if (connections) + { + logger->safeError("Tried to shutdown the network subsystem " + "while there are network connections left!"); + } + + clearNetworkHandlers(); + enet_deinitialize(); +} + +Connection *getConnection() +{ + if (!client) + { + logger->safeError("Tried to instantiate a network object before " + "initializing the network subsystem!"); + } + + return new Connection(client); +} + +void registerHandler(MessageHandler *handler) +{ + for (const Uint16 *i = handler->handledMessages; *i; i++) + mMessageHandlers[*i] = handler; +} + +void unregisterHandler(MessageHandler *handler) +{ + for (const Uint16 *i = handler->handledMessages; *i; i++) + mMessageHandlers.erase(*i); +} + +void clearNetworkHandlers() +{ + mMessageHandlers.clear(); +} + + +/** + * Dispatches a message to the appropriate message handler and + * destroys it afterwards. + */ +namespace +{ + void dispatchMessage(ENetPacket *packet) + { + MessageIn msg((const char *)packet->data, packet->dataLength); + + MessageHandlerIterator iter = mMessageHandlers.find(msg.getId()); + + if (iter != mMessageHandlers.end()) + { + //logger->log("Received packet %x (%i B)", + // msg.getId(), msg.getLength()); + iter->second->handleMessage(msg); + } + else + { + logger->log("Unhandled packet %x (%i B)", + msg.getId(), msg.getLength()); + } + + // Clean up the packet now that we're done using it. + enet_packet_destroy(packet); + } +} + +void flush() +{ + ENetEvent event; + + // Check if there are any new events + while (enet_host_service(client, &event, 0) > 0) + { + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: + logger->log("Connected to port %d.", event.peer->address.port); + // Store any relevant server information here. + event.peer->data = 0; + break; + + case ENET_EVENT_TYPE_RECEIVE: + dispatchMessage(event.packet); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + logger->log1("Disconnected."); + // Reset the server information. + event.peer->data = 0; + break; + + case ENET_EVENT_TYPE_NONE: + default: + break; + } + } +} + +} |