/* * 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 StringVect 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; } StringVect& 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.", static_cast<int>(tags.size()), static_cast<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