diff options
-rw-r--r-- | src/account-server/character.hpp | 16 | ||||
-rw-r--r-- | src/account-server/storage.cpp | 86 | ||||
-rw-r--r-- | src/account-server/storage.hpp | 2 | ||||
-rw-r--r-- | src/game-server/character.cpp | 25 | ||||
-rw-r--r-- | src/game-server/character.hpp | 26 | ||||
-rw-r--r-- | src/game-server/monster.cpp | 1 | ||||
-rw-r--r-- | src/game-server/trigger.cpp | 3 | ||||
-rw-r--r-- | src/scripting/lua.cpp | 29 | ||||
-rw-r--r-- | src/serialize/characterdata.hpp | 33 | ||||
-rw-r--r-- | src/sql/sqlite/createTables.sql | 13 | ||||
-rw-r--r-- | src/sql/sqlite/updates/update_7_to_8.sql | 20 |
11 files changed, 250 insertions, 4 deletions
diff --git a/src/account-server/character.hpp b/src/account-server/character.hpp index c99b2ff7..0aa7c8e1 100644 --- a/src/account-server/character.hpp +++ b/src/account-server/character.hpp @@ -140,6 +140,21 @@ class Character { return mStatusEffects.end(); } /** + * Get / Set kill count + */ + int getKillCountSize() const + { return mKillCount.size(); } + + const std::map<int, int>::const_iterator getKillCountBegin() const + { return mKillCount.begin(); } + + const std::map<int, int>::const_iterator getKillCountEnd() const + { return mKillCount.end(); } + + void setKillCount(int monsterId, int kills) + { mKillCount[monsterId] = kills; } + + /** * Gets the Id of the map that the character is on. */ int getMapId() const { return mMapId; } @@ -196,6 +211,7 @@ class Character unsigned short mAttributes[CHAR_ATTR_NB]; //!< Attributes. std::map<int, int> mExperience; //!< Skill Experience. std::map<int, int> mStatusEffects; //!< Status Effects + std::map<int, int> mKillCount; //!< Kill Count unsigned short mMapId; //!< Map the being is on. unsigned char mGender; //!< Gender of the being. unsigned char mHairStyle; //!< Hair style of the being. diff --git a/src/account-server/storage.cpp b/src/account-server/storage.cpp index e40e19da..c547977f 100644 --- a/src/account-server/storage.cpp +++ b/src/account-server/storage.cpp @@ -41,7 +41,7 @@ static const char *DEFAULT_ITEM_FILE = "data/items.xml"; // defines the supported db version static const char *DB_VERSION_PARAMETER = "database_version"; -static const char *SUPPORTED_DB_VERSION = "7"; +static const char *SUPPORTED_DB_VERSION = "8"; /* * MySQL specificities: @@ -72,6 +72,7 @@ static const char *ACCOUNTS_TBL_NAME = "mana_accounts"; static const char *CHARACTERS_TBL_NAME = "mana_characters"; static const char *CHAR_SKILLS_TBL_NAME = "mana_char_skills"; static const char *CHAR_STATUS_EFFECTS_TBL_NAME = "mana_char_status_effects"; +static const char *CHAR_KILL_COUNT_TBL_NAME = "mana_char_kill_stats"; static const char *INVENTORIES_TBL_NAME = "mana_inventories"; static const char *ITEMS_TBL_NAME = "mana_items"; static const char *GUILDS_TBL_NAME = "mana_guilds"; @@ -414,9 +415,9 @@ Character *Storage::getCharacterBySQL(Account *owner) toUint(skillInfo(row, 1))); // experience } } + // Load the status effect s.clear(); s.str(""); - // Load the status effect s << "select status_id, status_time FROM " << CHAR_STATUS_EFFECTS_TBL_NAME << " WHERE char_id = " << character->getDatabaseID(); const dal::RecordSet &statusInfo = mDb->execSql(s.str()); @@ -430,6 +431,23 @@ Character *Storage::getCharacterBySQL(Account *owner) toUint(statusInfo(row, 1))); // Time } } + // Load the kill stats + s.clear(); + s.str(""); + // Load the status effect + s << "select monster_id, kills FROM " << CHAR_KILL_COUNT_TBL_NAME + << " WHERE char_id = " << character->getDatabaseID(); + const dal::RecordSet &killsInfo = mDb->execSql(s.str()); + if (!statusInfo.isEmpty()) + { + const unsigned int nRows = killsInfo.rows(); + for (unsigned int row = 0; row < nRows; row++) + { + character->setKillCount( + toUint(killsInfo(row, 0)), // MonsterID + toUint(killsInfo(row, 1))); // Kills + } + } } catch (const dal::DbSqlQueryExecFailure &e) { @@ -693,7 +711,28 @@ bool Storage::updateCharacter(Character *character, return false; } - + /** + * Character's kill count + */ + try + { + std::map<int, int>::const_iterator kill_it; + for (kill_it = character->getKillCountBegin(); + kill_it != character->getKillCountEnd(); kill_it++) + { + updateKillCount(character->getDatabaseID(), kill_it->first, kill_it->second); + } + } + catch (const dal::DbSqlQueryExecFailure& e) + { + // TODO: throw an exception. + if (startTransaction) + { + mDb->rollbackTransaction(); + } + LOG_ERROR("(DALStorage::updateCharacter #2) SQL query failure: " << e.what()); + return false; + } /** * Character's inventory */ @@ -1128,6 +1167,47 @@ void Storage::updateExperience(int charId, int skillId, int skillValue) } /** + * Write a modification message about character skills to the database. + * @param CharId ID of the character + * @param monsterId ID of the monster type + * @param kills new amount of kills + */ +void Storage::updateKillCount(int charId, int monsterId, int kills) +{ + LOG_INFO("Updating kill counts"); //<- DELME + try + { + // try to update the kill count + std::ostringstream sql; + sql << "UPDATE " << CHAR_KILL_COUNT_TBL_NAME + << " SET kills = " << kills + << " WHERE char_id = " << charId + << " AND monster_id = " << monsterId; + mDb->execSql(sql.str()); + + // check if the update has modified a row + if (mDb->getModifiedRows() > 0) + { + return; + } + + sql.clear(); + sql.str(""); + sql << "INSERT INTO " << CHAR_KILL_COUNT_TBL_NAME << " " + << "(char_id, monster_id, kills) VALUES ( " + << charId << ", " + << monsterId << ", " + << kills << ")"; + mDb->execSql(sql.str()); + } + catch (const dal::DbSqlQueryExecFailure &e) + { + LOG_ERROR("DALStorage::updateKillCount: " << e.what()); + throw; + } +} + +/** * Inserts a record about a status effect into the database * @param charId ID of the character in the database * @param statusId ID of the status effect diff --git a/src/account-server/storage.hpp b/src/account-server/storage.hpp index c376e94f..bfc34c24 100644 --- a/src/account-server/storage.hpp +++ b/src/account-server/storage.hpp @@ -68,6 +68,8 @@ class Storage void updateExperience(int charId, int skillId, int skillValue); + void updateKillCount(int charId, int monsterId, int kills); + void insertStatusEffect(int charId, int statusId, int time); void banCharacter(int id, int duration); diff --git a/src/game-server/character.cpp b/src/game-server/character.cpp index 95082d6b..0775c75f 100644 --- a/src/game-server/character.cpp +++ b/src/game-server/character.cpp @@ -506,6 +506,31 @@ void Character::receiveExperience(int skill, int experience, int optimalLevel) } } +void Character::incrementKillCount(int monsterType) +{ + std::map<int, int>::iterator i = mKillCount.find(monsterType); + if (i == mKillCount.end()) + { + // character has never murdered this species before + mKillCount[monsterType] = 1; + } else { + // character is a repeated offender + mKillCount[monsterType] ++; + }; +} + +int Character::getKillCount(int monsterType) +{ + std::map<int, int>::iterator i = mKillCount.find(monsterType); + if (i == mKillCount.end()) + { + return 0; + } else { + return i->second; + }; +} + + void Character::recalculateLevel() { std::list<float> levels; diff --git a/src/game-server/character.hpp b/src/game-server/character.hpp index 8b04e8cb..05c36c24 100644 --- a/src/game-server/character.hpp +++ b/src/game-server/character.hpp @@ -291,6 +291,21 @@ class Character : public Being { return mStatusEffects.end(); } /** + * used to serialized kill count + */ + int getKillCountSize() const + { return mKillCount.size(); } + + const std::map<int, int>::const_iterator getKillCountBegin() const + { return mKillCount.begin(); } + + const std::map<int, int>::const_iterator getKillCountEnd() const + { return mKillCount.end(); } + + void setKillCount(int monsterId, int kills) + { mKillCount[monsterId] = kills; } + + /** * Gets total accumulated exp for skill */ int getExperience(int skill) const @@ -303,6 +318,16 @@ class Character : public Being { mExperience[skill] = 0; receiveExperience(skill, value, 0); } /** + * Adds one kill of the monster type to the characters kill count + */ + void incrementKillCount(int monsterType); + + /** + * Gets the number of monsters the character killed of a given type + */ + int getKillCount(int monsterType); + + /** * Shortcut to get being's health */ int getHealth() const @@ -425,6 +450,7 @@ class Character : public Being unsigned char mAccountLevel; /**< Account level of the user. */ int mParty; /**< Party id of the character */ TransactionType mTransaction; /**< Trade/buy/sell action the character is involved in. */ + std::map<int, int> mKillCount; /**< how many monsters the character has slayn of each type */ protected: /** diff --git a/src/game-server/monster.cpp b/src/game-server/monster.cpp index f30396c9..e0ff43b5 100644 --- a/src/game-server/monster.cpp +++ b/src/game-server/monster.cpp @@ -455,6 +455,7 @@ void Monster::died() { character->receiveExperience(*iSkill, expPerSkill, mSpecy->getOptimalLevel()); } + character->incrementKillCount(mSpecy->getType()); } } } diff --git a/src/game-server/trigger.cpp b/src/game-server/trigger.cpp index 522e0be1..7db53cca 100644 --- a/src/game-server/trigger.cpp +++ b/src/game-server/trigger.cpp @@ -52,6 +52,9 @@ void TriggerArea::update() std::set<Actor*> insideNow; for (BeingIterator i(getMap()->getInsideRectangleIterator(mZone)); i; ++i) { + //skip garbage + if (!(*i) || (*i)->getPublicID() == 0) continue; + if (mZone.contains((*i)->getPosition())) //<-- Why is this additional condition necessary? Shouldn't getInsideRectangleIterator already exclude those outside of the zone? --Crush { insideNow.insert(*i); diff --git a/src/scripting/lua.cpp b/src/scripting/lua.cpp index 1c79ede9..3b33a3d1 100644 --- a/src/scripting/lua.cpp +++ b/src/scripting/lua.cpp @@ -1292,6 +1292,34 @@ static int chr_get_hair_color(lua_State *s) } /** + * Get the number of monsters the player killed of a type + * mana.chr_get_kill_count (character, monsterType) + */ +static int chr_get_kill_count(lua_State *s) +{ + Character *c = getCharacter(s, 1); + if (!c) + { + raiseScriptError(s, "chr_get_kill_count called for nonexistent character."); + return 0; + } + + if (!lua_isnumber(s, 2)) + { + raiseScriptError(s, "chr_get_kill_count called with incorect parameters"); + return 0; + } + + int id = lua_tointeger(s, 2); + + int kills = c->getKillCount(id); + + lua_pushinteger(s, kills); + return 1; +} + + +/** * Returns the rights level of a character. * mana.chr_get_rights (being) */ @@ -1463,6 +1491,7 @@ LuaScript::LuaScript(): { "chr_get_hair_style", &chr_get_hair_style }, { "chr_set_hair_color", &chr_set_hair_color }, { "chr_get_hair_color", &chr_get_hair_color }, + { "chr_get_kill_count", &chr_get_kill_count }, { "exp_for_level", &exp_for_level }, { "monster_create", &monster_create }, { "monster_load_script", &monster_load_script }, diff --git a/src/serialize/characterdata.hpp b/src/serialize/characterdata.hpp index c2b7f7b0..1346f683 100644 --- a/src/serialize/characterdata.hpp +++ b/src/serialize/characterdata.hpp @@ -32,6 +32,7 @@ template< class T > void serializeCharacterData(const T &data, MessageOut &msg) { + // general character properties msg.writeByte(data.getAccountLevel()); msg.writeByte(data.getGender()); msg.writeByte(data.getHairStyle()); @@ -40,11 +41,13 @@ void serializeCharacterData(const T &data, MessageOut &msg) msg.writeShort(data.getCharacterPoints()); msg.writeShort(data.getCorrectionPoints()); + // character attributes for (int i = CHAR_ATTR_BEGIN; i < CHAR_ATTR_END; ++i) { msg.writeByte(data.getAttribute(i)); } + // character skills msg.writeShort(data.getSkillSize()); std::map<int, int>::const_iterator skill_it; @@ -54,6 +57,7 @@ void serializeCharacterData(const T &data, MessageOut &msg) msg.writeLong(skill_it->second); } + // status effects currently affecting the character msg.writeShort(data.getStatusEffectSize()); std::map<int, int>::const_iterator status_it; for (status_it = data.getStatusEffectBegin(); status_it != data.getStatusEffectEnd(); status_it++) @@ -62,12 +66,22 @@ void serializeCharacterData(const T &data, MessageOut &msg) msg.writeShort(status_it->second); } - + // location msg.writeShort(data.getMapId()); const Point &pos = data.getPosition(); msg.writeShort(pos.x); msg.writeShort(pos.y); + // kill count + msg.writeShort(data.getKillCountSize()); + std::map<int, int>::const_iterator kills_it; + for (kills_it = data.getKillCountBegin(); kills_it != data.getKillCountEnd(); kills_it++) + { + msg.writeShort(kills_it->first); + msg.writeLong(kills_it->second); + } + + // inventory - must be last because size isn't transmitted const Possessions &poss = data.getPossessions(); msg.writeLong(poss.money); for (int j = 0; j < EQUIPMENT_SLOTS; ++j) @@ -80,11 +94,13 @@ void serializeCharacterData(const T &data, MessageOut &msg) msg.writeShort(j->itemId); msg.writeByte(j->amount); } + } template< class T > void deserializeCharacterData(T &data, MessageIn &msg) { + // general character properties data.setAccountLevel(msg.readByte()); data.setGender(msg.readByte()); data.setHairStyle(msg.readByte()); @@ -93,11 +109,13 @@ void deserializeCharacterData(T &data, MessageIn &msg) data.setCharacterPoints(msg.readShort()); data.setCorrectionPoints(msg.readShort()); + // character attributes for (int i = CHAR_ATTR_BEGIN; i < CHAR_ATTR_END; ++i) { data.setAttribute(i, msg.readByte()); } + // character skills int skillSize = msg.readShort(); for (int i = 0; i < skillSize; ++i) @@ -107,6 +125,7 @@ void deserializeCharacterData(T &data, MessageIn &msg) data.setExperience(skill,level); } + // status effects currently affecting the character int statusSize = msg.readShort(); for (int i = 0; i < statusSize; i++) @@ -116,6 +135,7 @@ void deserializeCharacterData(T &data, MessageIn &msg) data.applyStatusEffect(status, time); } + // location data.setMapId(msg.readShort()); Point temporaryPoint; @@ -123,6 +143,16 @@ void deserializeCharacterData(T &data, MessageIn &msg) temporaryPoint.y = msg.readShort(); data.setPosition(temporaryPoint); + // kill count + int killSize = msg.readShort(); + for (int i = 0; i < killSize; i++) + { + int monsterId = msg.readShort(); + int kills = msg.readLong(); + data.setKillCount(monsterId, kills); + } + + // inventory - must be last because size isn't transmitted Possessions &poss = data.getPossessions(); poss.money = msg.readLong(); for (int j = 0; j < EQUIPMENT_SLOTS; ++j) @@ -137,6 +167,7 @@ void deserializeCharacterData(T &data, MessageIn &msg) i.amount = msg.readByte(); poss.inventory.push_back(i); } + } #endif diff --git a/src/sql/sqlite/createTables.sql b/src/sql/sqlite/createTables.sql index 26881fd7..e3222da9 100644 --- a/src/sql/sqlite/createTables.sql +++ b/src/sql/sqlite/createTables.sql @@ -97,6 +97,19 @@ CREATE INDEX mana_char_status_char on mana_char_status_effects ( char_id ); ----------------------------------------------------------------------------- +CREATE TABLE mana_char_kill_stats +( + char_id INTEGER NOT NULL, + monster_id INTEGER NOT NULL, + kills INTEGER NOT NULL, + -- + FOREIGN KEY (char_id) REFERENCES mana_characters(id) +); + +CREATE INDEX mana_char_kill_stats_char on mana_char_status_effects ( char_id ); + +----------------------------------------------------------------------------- + CREATE TABLE mana_items ( id INTEGER PRIMARY KEY, diff --git a/src/sql/sqlite/updates/update_7_to_8.sql b/src/sql/sqlite/updates/update_7_to_8.sql new file mode 100644 index 00000000..35593c67 --- /dev/null +++ b/src/sql/sqlite/updates/update_7_to_8.sql @@ -0,0 +1,20 @@ + +-- create table mana_char_kill_stats + +CREATE TABLE mana_char_kill_stats +( + char_id INTEGER NOT NULL, + monster_id INTEGER NOT NULL, + kills INTEGER NOT NULL, + -- + FOREIGN KEY (char_id) REFERENCES mana_characters(id) +); + +CREATE INDEX mana_char_kill_stats_chars on mana_char_kill_stats ( char_id ); + +-- update the database version, and set date of update +UPDATE mana_world_states + SET value = '8', + moddate = strftime('%s','now') + WHERE state_name = 'database_version'; + |