From 9a7030606a154d7640883b5137087c35a991a723 Mon Sep 17 00:00:00 2001 From: Sarah Wesker Date: Tue, 16 Apr 2024 03:26:14 -0400 Subject: [PATCH] pluralName fixed / experience rates --- src/condition.cpp | 101 +++++++++++++++++++++++++++++++++++++++++++--- src/condition.h | 8 ++++ src/item.h | 2 +- src/items.cpp | 64 +++++++++++++++++++++++++++++ src/items.h | 23 +++++++---- src/movement.cpp | 24 +++++++++++ src/player.cpp | 15 ++++--- 7 files changed, 217 insertions(+), 20 deletions(-) diff --git a/src/condition.cpp b/src/condition.cpp index 4d2baa2..e849830 100644 --- a/src/condition.cpp +++ b/src/condition.cpp @@ -377,6 +377,7 @@ void ConditionAttributes::addCondition(Creature* creature, const Condition* cond memcpy(skillsPercent, conditionAttrs.skillsPercent, sizeof(skillsPercent)); memcpy(stats, conditionAttrs.stats, sizeof(stats)); memcpy(statsPercent, conditionAttrs.statsPercent, sizeof(statsPercent)); + memcpy(experienceRate, conditionAttrs.experienceRate, sizeof(experienceRate)); disableDefense = conditionAttrs.disableDefense; if (Player* player = creature->getPlayer()) { @@ -396,6 +397,8 @@ bool ConditionAttributes::unserializeProp(ConditionAttr_t attr, PropStream& prop return propStream.read(specialSkills[currentSpecialSkill++]); } else if (attr == CONDITIONATTR_STATS) { return propStream.read(stats[currentStat++]); + } else if (attr == CONDITIONATTR_EXPERIENCERATE) { + return propStream.read(experienceRate[currentExperienceRate++]); } else if (attr == CONDITIONATTR_DISABLEDEFENSE) { return propStream.read(disableDefense); } @@ -423,6 +426,12 @@ void ConditionAttributes::serialize(PropWriteStream& propWriteStream) propWriteStream.write(CONDITIONATTR_SPECIALSKILLS); propWriteStream.write(specialSkills[i]); } + + for (uint8_t i = static_cast(ExperienceRateType::BASE); + i <= static_cast(ExperienceRateType::STAMINA); ++i) { + propWriteStream.write(CONDITIONATTR_SPECIALSKILLS); + propWriteStream.write(experienceRate[i]); + } } bool ConditionAttributes::startCondition(Creature* creature) @@ -438,6 +447,7 @@ bool ConditionAttributes::startCondition(Creature* creature) updateSkills(player); updatePercentStats(player); updateStats(player); + updateExperienceRate(player); } return true; @@ -518,6 +528,16 @@ void ConditionAttributes::updateSkills(Player* player) } } +void ConditionAttributes::updateExperienceRate(Player* player) +{ + for (uint8_t i = static_cast(ExperienceRateType::BASE); + i <= static_cast(ExperienceRateType::STAMINA); ++i) { + if (experienceRate[i] != 1.0) { + player->setExperienceRate(static_cast(i), experienceRate[i]); + } + } +} + bool ConditionAttributes::executeCondition(Creature* creature, int32_t interval) { return ConditionGeneric::executeCondition(creature, interval); @@ -560,6 +580,13 @@ void ConditionAttributes::endCondition(Creature* creature) player->sendStats(); player->sendSkills(); } + + for (uint8_t i = static_cast(ExperienceRateType::BASE); + i <= static_cast(ExperienceRateType::STAMINA); ++i) { + if (experienceRate[i] != 1.0) { + player->setExperienceRate(static_cast(i), -experienceRate[i]); + } + } } if (disableDefense) { @@ -716,6 +743,26 @@ bool ConditionAttributes::setParam(ConditionParam_t param, int32_t value) return true; } + case CONDITION_PARAM_EXPERIENCERATE_BASE: { + experienceRate[static_cast(ExperienceRateType::BASE)] = value; + return true; + } + + case CONDITION_PARAM_EXPERIENCERATE_LOW_LEVEL: { + experienceRate[static_cast(ExperienceRateType::LOW_LEVEL)] = value; + return true; + } + + case CONDITION_PARAM_EXPERIENCERATE_BONUS: { + experienceRate[static_cast(ExperienceRateType::BONUS)] = value; + return true; + } + + case CONDITION_PARAM_EXPERIENCERATE_STAMINA: { + experienceRate[static_cast(ExperienceRateType::STAMINA)] = value; + return true; + } + case CONDITION_PARAM_DISABLE_DEFENSE: { disableDefense = (value != 0); return true; @@ -815,6 +862,18 @@ int32_t ConditionAttributes::getParam(ConditionParam_t param) const case CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT: return specialSkills[SPECIALSKILL_MANALEECHAMOUNT]; + case CONDITION_PARAM_EXPERIENCERATE_BASE: + return experienceRate[static_cast(ExperienceRateType::BASE)]; + + case CONDITION_PARAM_EXPERIENCERATE_LOW_LEVEL: + return experienceRate[static_cast(ExperienceRateType::LOW_LEVEL)]; + + case CONDITION_PARAM_EXPERIENCERATE_BONUS: + return experienceRate[static_cast(ExperienceRateType::BONUS)]; + + case CONDITION_PARAM_EXPERIENCERATE_STAMINA: + return experienceRate[static_cast(ExperienceRateType::STAMINA)]; + default: return ConditionGeneric::getParam(param); } @@ -832,6 +891,9 @@ void ConditionRegeneration::addCondition(Creature*, const Condition* condition) healthGain = conditionRegen.healthGain; manaGain = conditionRegen.manaGain; + + healthGainPercent = conditionRegen.healthGainPercent; + manaGainPercent = conditionRegen.manaGainPercent; } } @@ -841,10 +903,14 @@ bool ConditionRegeneration::unserializeProp(ConditionAttr_t attr, PropStream& pr return propStream.read(healthTicks); } else if (attr == CONDITIONATTR_HEALTHGAIN) { return propStream.read(healthGain); + } else if (attr == CONDITIONATTR_HEALTHGAINPERCENT) { + return propStream.read(healthGainPercent); } else if (attr == CONDITIONATTR_MANATICKS) { return propStream.read(manaTicks); } else if (attr == CONDITIONATTR_MANAGAIN) { return propStream.read(manaGain); + } else if (attr == CONDITIONATTR_MANAGAINPERCENT) { + return propStream.read(manaGainPercent); } return Condition::unserializeProp(attr, propStream); } @@ -859,11 +925,17 @@ void ConditionRegeneration::serialize(PropWriteStream& propWriteStream) propWriteStream.write(CONDITIONATTR_HEALTHGAIN); propWriteStream.write(healthGain); + propWriteStream.write(CONDITIONATTR_HEALTHGAINPERCENT); + propWriteStream.write(healthGainPercent); + propWriteStream.write(CONDITIONATTR_MANATICKS); propWriteStream.write(manaTicks); propWriteStream.write(CONDITIONATTR_MANAGAIN); propWriteStream.write(manaGain); + + propWriteStream.write(CONDITIONATTR_MANAGAINPERCENT); + propWriteStream.write(manaGainPercent); } bool ConditionRegeneration::executeCondition(Creature* creature, int32_t interval) @@ -878,13 +950,18 @@ bool ConditionRegeneration::executeCondition(Creature* creature, int32_t interva if (internalHealthTicks >= healthTicks) { internalHealthTicks = 0; + uint32_t healAmount = healthGain; + if (healthGainPercent != 0) { + auto percent = static_cast(healthGainPercent - 100) / 100.0f; + healAmount += static_cast(creature->getMaxHealth() * percent); + } + int32_t realHealthGain = creature->getHealth(); - creature->changeHealth(healthGain); + creature->changeHealth(healAmount); realHealthGain = creature->getHealth() - realHealthGain; if (isBuff && realHealthGain > 0) { - Player* player = creature->getPlayer(); - if (player) { + if (auto player = creature->getPlayer()) { std::string healString = std::to_string(realHealthGain) + (realHealthGain != 1 ? " hitpoints." : " hitpoint."); @@ -912,9 +989,15 @@ bool ConditionRegeneration::executeCondition(Creature* creature, int32_t interva if (internalManaTicks >= manaTicks) { internalManaTicks = 0; - if (Player* player = creature->getPlayer()) { + if (auto player = creature->getPlayer()) { + uint32_t manaAmount = manaGain; + if (manaGainPercent != 0) { + auto percent = static_cast(manaGainPercent - 100) / 100.0f; + manaAmount += static_cast(player->getMaxMana() * percent); + } + int32_t realManaGain = player->getMana(); - player->changeMana(manaGain); + player->changeMana(manaAmount); realManaGain = player->getMana() - realManaGain; if (isBuff && realManaGain > 0) { @@ -953,6 +1036,10 @@ bool ConditionRegeneration::setParam(ConditionParam_t param, int32_t value) healthGain = value; return true; + case CONDITION_PARAM_HEALTHGAINPERCENT: + healthGainPercent = value; + return true; + case CONDITION_PARAM_HEALTHTICKS: healthTicks = value; return true; @@ -961,6 +1048,10 @@ bool ConditionRegeneration::setParam(ConditionParam_t param, int32_t value) manaGain = value; return true; + case CONDITION_PARAM_MANAGAINPERCENT: + manaGainPercent = value; + return true; + case CONDITION_PARAM_MANATICKS: manaTicks = value; return true; diff --git a/src/condition.h b/src/condition.h index 9eaee72..82168cf 100644 --- a/src/condition.h +++ b/src/condition.h @@ -43,6 +43,9 @@ enum ConditionAttr_t CONDITIONATTR_ISAGGRESSIVE, CONDITIONATTR_DISABLEDEFENSE, CONDITIONATTR_SPECIALSKILLS, + CONDITIONATTR_EXPERIENCERATE, + CONDITIONATTR_HEALTHGAINPERCENT, + CONDITIONATTR_MANAGAINPERCENT, // reserved for serialization CONDITIONATTR_END = 254, @@ -160,9 +163,11 @@ class ConditionAttributes final : public ConditionGeneric int32_t specialSkills[SPECIALSKILL_LAST + 1] = {}; int32_t stats[STAT_LAST + 1] = {}; int32_t statsPercent[STAT_LAST + 1] = {}; + int32_t experienceRate[static_cast(ExperienceRateType::STAMINA) + 1] = {}; int32_t currentSkill = 0; int32_t currentSpecialSkill = 0; int32_t currentStat = 0; + int32_t currentExperienceRate = 0; bool disableDefense = false; @@ -170,6 +175,7 @@ class ConditionAttributes final : public ConditionGeneric void updateStats(Player* player); void updatePercentSkills(Player* player); void updateSkills(Player* player); + void updateExperienceRate(Player* player); }; class ConditionRegeneration final : public ConditionGeneric @@ -199,7 +205,9 @@ class ConditionRegeneration final : public ConditionGeneric uint32_t healthTicks = 1000; uint32_t manaTicks = 1000; uint32_t healthGain = 0; + uint32_t healthGainPercent = 0; uint32_t manaGain = 0; + uint32_t manaGainPercent = 0; }; class ConditionSoul final : public ConditionGeneric diff --git a/src/item.h b/src/item.h index ec8b1f0..af77a10 100644 --- a/src/item.h +++ b/src/item.h @@ -840,7 +840,7 @@ class Item : virtual public Thing if (hasAttribute(ITEM_ATTRIBUTE_PLURALNAME)) { return getStrAttr(ITEM_ATTRIBUTE_PLURALNAME); } - return items[id].pluralName; + return items[id].getPluralName(); } std::string_view getArticle() const { diff --git a/src/items.cpp b/src/items.cpp index ccc18b6..4f37141 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -64,8 +64,10 @@ const std::unordered_map ItemParseAttributes {"invisible", ITEM_PARSE_INVISIBLE}, {"speed", ITEM_PARSE_SPEED}, {"healthgain", ITEM_PARSE_HEALTHGAIN}, + {"healthgainpercent", ITEM_PARSE_HEALTHGAINPERCENT}, {"healthticks", ITEM_PARSE_HEALTHTICKS}, {"managain", ITEM_PARSE_MANAGAIN}, + {"managainpercent", ITEM_PARSE_MANAGAINPERCENT}, {"manaticks", ITEM_PARSE_MANATICKS}, {"manashield", ITEM_PARSE_MANASHIELD}, {"skillsword", ITEM_PARSE_SKILLSWORD}, @@ -201,6 +203,10 @@ const std::unordered_map ItemParseAttributes {"storeitem", ITEM_PARSE_STOREITEM}, {"worth", ITEM_PARSE_WORTH}, {"supply", ITEM_PARSE_SUPPLY}, + {"experienceratebase", ITEM_PARSE_EXPERIENCERATE_BASE}, + {"experienceratelowlevel", ITEM_PARSE_EXPERIENCERATE_LOW_LEVEL}, + {"experienceratebonus", ITEM_PARSE_EXPERIENCERATE_BONUS}, + {"experienceratestamina", ITEM_PARSE_EXPERIENCERATE_STAMINA}, }; const std::unordered_map ItemTypesMap = {{"key", ITEM_TYPE_KEY}, @@ -979,6 +985,12 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) break; } + case ITEM_PARSE_HEALTHGAINPERCENT: { + abilities.regeneration = true; + abilities.healthGainPercent = pugi::cast(valueAttribute.value()); + break; + } + case ITEM_PARSE_HEALTHTICKS: { abilities.regeneration = true; abilities.healthTicks = pugi::cast(valueAttribute.value()); @@ -991,6 +1003,12 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) break; } + case ITEM_PARSE_MANAGAINPERCENT: { + abilities.regeneration = true; + abilities.manaGainPercent = pugi::cast(valueAttribute.value()); + break; + } + case ITEM_PARSE_MANATICKS: { abilities.regeneration = true; abilities.manaTicks = pugi::cast(valueAttribute.value()); @@ -1847,6 +1865,30 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) break; } + case ITEM_PARSE_EXPERIENCERATE_BASE: { + int32_t rate = pugi::cast(valueAttribute.value()); + abilities.experienceRate[static_cast(ExperienceRateType::BASE)] = rate; + break; + } + + case ITEM_PARSE_EXPERIENCERATE_LOW_LEVEL: { + int32_t rate = pugi::cast(valueAttribute.value()); + abilities.experienceRate[static_cast(ExperienceRateType::LOW_LEVEL)] = rate; + break; + } + + case ITEM_PARSE_EXPERIENCERATE_BONUS: { + int32_t rate = pugi::cast(valueAttribute.value()); + abilities.experienceRate[static_cast(ExperienceRateType::BONUS)] = rate; + break; + } + + case ITEM_PARSE_EXPERIENCERATE_STAMINA: { + int32_t rate = pugi::cast(valueAttribute.value()); + abilities.experienceRate[static_cast(ExperienceRateType::STAMINA)] = rate; + break; + } + default: { // It should not ever get to here, only if you add a new key to the map and don't configure a case // for it. @@ -1921,3 +1963,25 @@ uint16_t Items::getItemIdByName(const std::string& name) return result->second; } + +std::string ItemType::pluralString; + +std::string_view ItemType::getPluralName() const +{ + if (!pluralName.empty()) { + return pluralName; + } + + if (showCount == 0) { + return name; + } + + if (name.empty() || name.back() == 's') { + return name; + } + + pluralString.reserve(name.size() + 1); + pluralString.assign(name); + pluralString.push_back('s'); + return pluralString; +} diff --git a/src/items.h b/src/items.h index 5053f61..c80b6b6 100644 --- a/src/items.h +++ b/src/items.h @@ -89,8 +89,10 @@ enum ItemParseAttributes_t ITEM_PARSE_INVISIBLE, ITEM_PARSE_SPEED, ITEM_PARSE_HEALTHGAIN, + ITEM_PARSE_HEALTHGAINPERCENT, ITEM_PARSE_HEALTHTICKS, ITEM_PARSE_MANAGAIN, + ITEM_PARSE_MANAGAINPERCENT, ITEM_PARSE_MANATICKS, ITEM_PARSE_MANASHIELD, ITEM_PARSE_SKILLSWORD, @@ -213,13 +215,19 @@ enum ItemParseAttributes_t ITEM_PARSE_BOOSTPERCENTPHYSICAL, ITEM_PARSE_BOOSTPERCENTHEALING, ITEM_PARSE_SUPPLY, + ITEM_PARSE_EXPERIENCERATE_BASE, + ITEM_PARSE_EXPERIENCERATE_LOW_LEVEL, + ITEM_PARSE_EXPERIENCERATE_BONUS, + ITEM_PARSE_EXPERIENCERATE_STAMINA, }; struct Abilities { uint32_t healthGain = 0; + uint32_t healthGainPercent = 0; uint32_t healthTicks = 0; uint32_t manaGain = 0; + uint32_t manaGainPercent = 0; uint32_t manaTicks = 0; uint32_t conditionImmunities = 0; @@ -244,6 +252,9 @@ struct Abilities std::array reflect; + // experience rates + std::array(ExperienceRateType::STAMINA) + 1> experienceRate = {0}; + int16_t boostPercent[COMBAT_COUNT] = {0}; // elemental damage @@ -296,14 +307,7 @@ class ItemType return *abilities; } - std::string_view getPluralName() const - { - if (showCount == 0) { - return name; - } - - return pluralName; - } + std::string_view getPluralName() const; itemgroup_t group = ITEM_GROUP_NONE; ItemTypes_t type = ITEM_TYPE_NONE; @@ -400,6 +404,9 @@ class ItemType bool stopTime = false; bool showCount = true; bool supply = false; + +private: + static std::string pluralString; }; class Items diff --git a/src/movement.cpp b/src/movement.cpp index b1c8cdd..b881490 100644 --- a/src/movement.cpp +++ b/src/movement.cpp @@ -745,6 +745,10 @@ ReturnValue MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* ite condition->setParam(CONDITION_PARAM_HEALTHGAIN, it.abilities->healthGain); } + if (it.abilities->healthGainPercent != 0) { + condition->setParam(CONDITION_PARAM_HEALTHGAINPERCENT, it.abilities->healthGainPercent); + } + if (it.abilities->healthTicks != 0) { condition->setParam(CONDITION_PARAM_HEALTHTICKS, it.abilities->healthTicks); } @@ -753,6 +757,10 @@ ReturnValue MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* ite condition->setParam(CONDITION_PARAM_MANAGAIN, it.abilities->manaGain); } + if (it.abilities->manaGainPercent != 0) { + condition->setParam(CONDITION_PARAM_MANAGAINPERCENT, it.abilities->manaGainPercent); + } + if (it.abilities->manaTicks != 0) { condition->setParam(CONDITION_PARAM_MANATICKS, it.abilities->manaTicks); } @@ -809,6 +817,14 @@ ReturnValue MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* ite player->sendSkills(); } + // experience rates + for (uint8_t e = static_cast(ExperienceRateType::BASE); + e <= static_cast(ExperienceRateType::STAMINA); ++e) { + if (it.abilities->experienceRate[e] != 0) { + player->addExperienceRate(static_cast(e), it.abilities->experienceRate[e]); + } + } + return RETURNVALUE_NOERROR; } @@ -900,6 +916,14 @@ ReturnValue MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots player->sendSkills(); } + // experience rates + for (uint8_t e = static_cast(ExperienceRateType::BASE); + e <= static_cast(ExperienceRateType::STAMINA); ++e) { + if (it.abilities->experienceRate[e] != 0) { + player->addExperienceRate(static_cast(e), -it.abilities->experienceRate[e]); + } + } + return RETURNVALUE_NOERROR; } diff --git a/src/player.cpp b/src/player.cpp index 1cb154a..bdf13ee 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -29,7 +29,10 @@ MuteCountMap Player::muteCountMap; uint32_t Player::playerAutoID = 0x10000000; -Player::Player(ProtocolGame_ptr p) : Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), client(std::move(p)) {} +Player::Player(ProtocolGame_ptr p) : Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), client(std::move(p)) +{ + experienceRate.fill(100); +} Player::~Player() { @@ -379,10 +382,10 @@ uint16_t Player::getClientIcons() const if (tile && tile->hasFlag(TILESTATE_PROTECTIONZONE)) { icons |= ICON_PIGEON; - // Don't show ICON_SWORDS if player is in protection zone. + /* Don't show ICON_SWORDS if player is in protection zone. if (hasBitSet(ICON_SWORDS, icons)) { - icons &= ~ICON_SWORDS; - } + icons &= ~ICON_SWORDS; + }*/ } // Game client debugs with 10 or more icons @@ -1712,8 +1715,8 @@ uint16_t Player::getBasisPointLevel(uint64_t count, uint64_t nextLevelCount) return 0; } - uint16_t result = ((count * 10000.) / nextLevelCount); - if (result > 10000) { + uint16_t result = ((count * 100.) / nextLevelCount); + if (result > 100) { return 0; } return result;