From 790e15f2e64badf62d9ba62421776c4ba0e771ed Mon Sep 17 00:00:00 2001 From: Lane Kolbly Date: Fri, 28 Jul 2017 12:00:20 -0500 Subject: Added anvil enchantment handling. (#3857) + Added anvil enchantment handling. --- src/Enchantments.cpp | 208 ++++++++++++++++++++++++++++++++++++++++++++------- src/Enchantments.h | 20 ++++- src/Item.cpp | 199 +++++++++++++++++++++++++++++++++++++++++++++++- src/Item.h | 17 ++++- src/UI/SlotArea.cpp | 9 ++- 5 files changed, 418 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/Enchantments.cpp b/src/Enchantments.cpp index a18f6d68a..0047d09b8 100644 --- a/src/Enchantments.cpp +++ b/src/Enchantments.cpp @@ -167,6 +167,164 @@ bool cEnchantments::IsEmpty(void) const +unsigned int cEnchantments::GetLevelCap(int a_EnchantmentID) +{ + switch (a_EnchantmentID) + { + case enchProtection: return 4; + case enchFireProtection: return 4; + case enchFeatherFalling: return 4; + case enchBlastProtection: return 4; + case enchProjectileProtection: return 4; + case enchRespiration: return 3; + case enchAquaAffinity: return 1; + case enchThorns: return 3; + case enchDepthStrider: return 3; + case enchSharpness: return 5; + case enchSmite: return 5; + case enchBaneOfArthropods: return 5; + case enchKnockback: return 2; + case enchFireAspect: return 2; + case enchLooting: return 3; + case enchEfficiency: return 5; + case enchSilkTouch: return 1; + case enchUnbreaking: return 3; + case enchFortune: return 3; + case enchPower: return 5; + case enchPunch: return 2; + case enchFlame: return 1; + case enchInfinity: return 1; + case enchLuckOfTheSea: return 3; + case enchLure: return 3; + } + LOGWARNING("Unknown enchantment ID %d", a_EnchantmentID); + return 0; +} + + + + + +int cEnchantments::GetXPCostMultiplier(int a_EnchantmentID, bool FromBook) +{ + if (FromBook) + { + switch (a_EnchantmentID) + { + case enchProtection: return 1; + case enchFireProtection: return 1; + case enchFeatherFalling: return 1; + case enchBlastProtection: return 2; + case enchProjectileProtection: return 1; + case enchRespiration: return 2; + case enchAquaAffinity: return 2; + case enchThorns: return 4; + case enchDepthStrider: return 2; + case enchSharpness: return 1; + case enchSmite: return 1; + case enchBaneOfArthropods: return 1; + case enchKnockback: return 1; + case enchFireAspect: return 2; + case enchLooting: return 2; + case enchEfficiency: return 1; + case enchSilkTouch: return 4; + case enchUnbreaking: return 1; + case enchFortune: return 1; + case enchPower: return 1; + case enchPunch: return 2; + case enchFlame: return 2; + case enchInfinity: return 4; + case enchLuckOfTheSea: return 2; + case enchLure: return 2; + } + } + else // Without book + { + switch (a_EnchantmentID) + { + case enchProtection: return 1; + case enchFireProtection: return 2; + case enchFeatherFalling: return 2; + case enchBlastProtection: return 4; + case enchProjectileProtection: return 2; + case enchRespiration: return 4; + case enchAquaAffinity: return 4; + case enchThorns: return 8; + case enchDepthStrider: return 4; + + case enchSharpness: return 1; + case enchSmite: return 2; + case enchBaneOfArthropods: return 2; + case enchKnockback: return 2; + case enchFireAspect: return 4; + case enchLooting: return 4; + + case enchEfficiency: return 1; + case enchSilkTouch: return 8; + case enchUnbreaking: return 2; + case enchFortune: return 4; + case enchPower: return 1; + case enchPunch: return 4; + case enchFlame: return 4; + case enchInfinity: return 8; + case enchLuckOfTheSea: return 4; + case enchLure: return 4; + } + } + LOGWARNING("Unknown enchantment ID %d", a_EnchantmentID); + return 0; +} + + + + + +bool cEnchantments::CanAddEnchantment(int a_EnchantmentID) const +{ + if (GetLevel(a_EnchantmentID) > 0) + { + return true; + } + + static const std::vector > IncompatibleEnchantments = + { + // Armor + { enchProtection, enchFireProtection, enchBlastProtection, enchProjectileProtection }, + + // Tool + { enchFortune, enchSilkTouch }, + + // Sword + { enchSharpness, enchSmite, enchBaneOfArthropods }, + + // Boots + // {enchDepthStrider, enchFrostWalker}, + + // Bow + // {enchInfinity, enchMending} + }; + + for (auto excl: IncompatibleEnchantments) + { + if (excl.count(a_EnchantmentID) != 0) + { + // See if we also have any of the enchantments + for (auto ench: excl) + { + if (GetLevel(ench) > 0) + { + return false; + } + } + } + } + return true; +} + + + + + int cEnchantments::StringToEnchantmentID(const AString & a_EnchantmentName) { static const struct @@ -175,31 +333,31 @@ int cEnchantments::StringToEnchantmentID(const AString & a_EnchantmentName) const char * m_Name; } EnchantmentNames[] = { - { enchProtection, "Protection"}, - { enchFireProtection, "FireProtection"}, - { enchFeatherFalling, "FeatherFalling"}, - { enchBlastProtection, "BlastProtection"}, - { enchProjectileProtection, "ProjectileProtection"}, - { enchRespiration, "Respiration"}, - { enchAquaAffinity, "AquaAffinity"}, - { enchThorns, "Thorns"}, - { enchDepthStrider, "DepthStrider"}, - { enchSharpness, "Sharpness"}, - { enchSmite, "Smite"}, - { enchBaneOfArthropods, "BaneOfArthropods"}, - { enchKnockback, "Knockback"}, - { enchFireAspect, "FireAspect"}, - { enchLooting, "Looting"}, - { enchEfficiency, "Efficiency"}, - { enchSilkTouch, "SilkTouch"}, - { enchUnbreaking, "Unbreaking"}, - { enchFortune, "Fortune"}, - { enchPower, "Power"}, - { enchPunch, "Punch"}, - { enchFlame, "Flame"}, - { enchInfinity, "Infinity"}, - { enchLuckOfTheSea, "LuckOfTheSea"}, - { enchLure, "Lure"}, + { enchProtection, "Protection" }, + { enchFireProtection, "FireProtection" }, + { enchFeatherFalling, "FeatherFalling" }, + { enchBlastProtection, "BlastProtection" }, + { enchProjectileProtection, "ProjectileProtection" }, + { enchRespiration, "Respiration" }, + { enchAquaAffinity, "AquaAffinity" }, + { enchThorns, "Thorns" }, + { enchDepthStrider, "DepthStrider" }, + { enchSharpness, "Sharpness" }, + { enchSmite, "Smite" }, + { enchBaneOfArthropods, "BaneOfArthropods" }, + { enchKnockback, "Knockback" }, + { enchFireAspect, "FireAspect" }, + { enchLooting, "Looting" }, + { enchEfficiency, "Efficiency" }, + { enchSilkTouch, "SilkTouch" }, + { enchUnbreaking, "Unbreaking" }, + { enchFortune, "Fortune" }, + { enchPower, "Power" }, + { enchPunch, "Punch" }, + { enchFlame, "Flame" }, + { enchInfinity, "Infinity" }, + { enchLuckOfTheSea, "LuckOfTheSea" }, + { enchLure, "Lure" }, } ; // First try to parse as a number: diff --git a/src/Enchantments.h b/src/Enchantments.h index 1119f7e2f..3f7b1bfc8 100644 --- a/src/Enchantments.h +++ b/src/Enchantments.h @@ -45,6 +45,7 @@ public: enum eEnchantment { + // Currently missing: Frost walker, curse of binding, sweeping edge, mending, and curse of vanishing. enchProtection = 0, enchFireProtection = 1, enchFeatherFalling = 2, @@ -103,6 +104,9 @@ public: /** Returns true if there are no enchantments */ bool IsEmpty(void) const; + /** Returns true if the given enchantment could be legally added to this object. Note that adding the enchantment may not actually increase the level. */ + bool CanAddEnchantment(int a_EnchantmentID) const; + /** Converts enchantment name or ID (number in string) to the numeric representation; returns -1 if enchantment name not found; case insensitive */ static int StringToEnchantmentID(const AString & a_EnchantmentName); @@ -111,6 +115,15 @@ public: // tolua_end + /** Get the XP cost multiplier for the enchantment (for anvils). + If FromBook is true, then this function returns the XP multiplier if + the enchantment is coming from a book, otherwise it returns the normal + item multiplier. */ + static int GetXPCostMultiplier(int a_EnchantmentID, bool FromBook); + + /** Get the maximum level the enchantment can have */ + static unsigned int GetLevelCap(int a_EnchantmentID); + /** Add enchantment weights from item to the vector */ static void AddItemEnchantmentWeights(cWeightedEnchantments & a_Enchantments, short a_ItemType, int a_EnchantmentLevel); @@ -149,7 +162,12 @@ protected: /** Currently stored enchantments */ cMap m_Enchantments; -} ; // tolua_export + +public: + /** Make this class iterable */ + cMap::const_iterator begin() const { return m_Enchantments.begin(); } + cMap::const_iterator end() const { return m_Enchantments.end(); } +}; // tolua_export diff --git a/src/Item.cpp b/src/Item.cpp index d421a95ad..3d9efb3b3 100644 --- a/src/Item.cpp +++ b/src/Item.cpp @@ -228,14 +228,14 @@ void cItem::FromJson(const Json::Value & a_Value) -bool cItem::IsEnchantable(short a_ItemType, bool a_WithBook) +bool cItem::IsEnchantable(short a_ItemType, bool a_FromBook) { if ( ItemCategory::IsAxe(a_ItemType) || ItemCategory::IsSword(a_ItemType) || ItemCategory::IsShovel(a_ItemType) || ItemCategory::IsPickaxe(a_ItemType) || - (a_WithBook && ItemCategory::IsHoe(a_ItemType)) || + (a_FromBook && ItemCategory::IsHoe(a_ItemType)) || ItemCategory::IsArmor(a_ItemType) ) { @@ -255,7 +255,7 @@ bool cItem::IsEnchantable(short a_ItemType, bool a_WithBook) case E_ITEM_SHEARS: case E_ITEM_FLINT_AND_STEEL: { - return a_WithBook; + return a_FromBook; } } @@ -419,6 +419,199 @@ bool cItem::EnchantByXPLevels(int a_NumXPLevels) +int cItem::AddEnchantment(int a_EnchantmentID, unsigned int a_Level, bool a_FromBook) +{ + unsigned int OurLevel = m_Enchantments.GetLevel(a_EnchantmentID); + int Multiplier = cEnchantments::GetXPCostMultiplier(a_EnchantmentID, a_FromBook); + unsigned int NewLevel = 0; + if (OurLevel > a_Level) + { + // They don't add anything to us + NewLevel = OurLevel; + } + else if (OurLevel == a_Level) + { + // Bump it by 1 + NewLevel = OurLevel + 1; + } + else + { + // Take the sacrifice's level + NewLevel = a_Level; + } + unsigned int LevelCap = cEnchantments::GetLevelCap(a_EnchantmentID); + if (NewLevel > LevelCap) + { + NewLevel = LevelCap; + } + + m_Enchantments.SetLevel(a_EnchantmentID, NewLevel); + return static_cast(NewLevel) * Multiplier; +} + + + + + +bool cItem::CanHaveEnchantment(int a_EnchantmentID) +{ + if (m_ItemType == E_ITEM_ENCHANTED_BOOK) + { + // Enchanted books can take anything + return true; + } + + // The organization here is based on the summary at: + // http://minecraft.gamepedia.com/Enchanting + // as of July 2017 (Minecraft 1.12). + + // Hand tool enchantments + static const std::set SwordEnchantments = + { + cEnchantments::enchBaneOfArthropods, + cEnchantments::enchFireAspect, + cEnchantments::enchKnockback, + cEnchantments::enchLooting, + cEnchantments::enchSharpness, + cEnchantments::enchSmite, + cEnchantments::enchUnbreaking + }; + static const std::set AxeEnchantments = + { + cEnchantments::enchBaneOfArthropods, + cEnchantments::enchEfficiency, + cEnchantments::enchFortune, + cEnchantments::enchSharpness, + cEnchantments::enchSilkTouch, + cEnchantments::enchSmite, + cEnchantments::enchUnbreaking + }; + static const std::set ToolEnchantments = + { + cEnchantments::enchEfficiency, + cEnchantments::enchFortune, + cEnchantments::enchSilkTouch, + cEnchantments::enchUnbreaking + }; + static const std::set ShearEnchantments = + { + cEnchantments::enchEfficiency, + cEnchantments::enchUnbreaking + }; + static const std::set BowEnchantments = + { + cEnchantments::enchFlame, + cEnchantments::enchInfinity, + cEnchantments::enchPower, + cEnchantments::enchPunch + }; + static const std::set FishingEnchantments = + { + cEnchantments::enchLuckOfTheSea, + cEnchantments::enchLure + }; + static const std::set MiscEnchantments = + { + cEnchantments::enchUnbreaking + }; + + if (ItemCategory::IsSword(m_ItemType)) + { + return SwordEnchantments.count(a_EnchantmentID) > 0; + } + if (ItemCategory::IsAxe(m_ItemType)) + { + return AxeEnchantments.count(a_EnchantmentID) > 0; + } + if (ItemCategory::IsPickaxe(m_ItemType) || ItemCategory::IsShovel(m_ItemType)) + { + return ToolEnchantments.count(a_EnchantmentID) > 0; + } + if (m_ItemType == E_ITEM_SHEARS) + { + return ShearEnchantments.count(a_EnchantmentID) > 0; + } + if (m_ItemType == E_ITEM_BOW) + { + return BowEnchantments.count(a_EnchantmentID) > 0; + } + if (m_ItemType == E_ITEM_FISHING_ROD) + { + return FishingEnchantments.count(a_EnchantmentID) > 0; + } + if (ItemCategory::IsHoe(m_ItemType) || (m_ItemType == E_ITEM_FLINT_AND_STEEL) || (m_ItemType == E_ITEM_CARROT_ON_STICK) || (m_ItemType == E_ITEM_SHIELD)) + { + return MiscEnchantments.count(a_EnchantmentID) > 0; + } + + // Armor enchantments + static const std::set ArmorEnchantments = + { + cEnchantments::enchBlastProtection, + cEnchantments::enchFireProtection, + cEnchantments::enchProjectileProtection, + cEnchantments::enchProtection, + cEnchantments::enchThorns, + cEnchantments::enchUnbreaking + }; + static const std::set HatOnlyEnchantments = + { + cEnchantments::enchAquaAffinity, + cEnchantments::enchRespiration + }; + static const std::set BootOnlyEnchantments = + { + cEnchantments::enchDepthStrider, + cEnchantments::enchFeatherFalling + }; + + if (ItemCategory::IsBoots(m_ItemType)) + { + return (BootOnlyEnchantments.count(a_EnchantmentID) > 0) || (ArmorEnchantments.count(a_EnchantmentID) > 0); + } + if (ItemCategory::IsHelmet(m_ItemType)) + { + return (HatOnlyEnchantments.count(a_EnchantmentID) > 0) || (ArmorEnchantments.count(a_EnchantmentID) > 0); + } + if (ItemCategory::IsArmor(m_ItemType)) + { + return ArmorEnchantments.count(a_EnchantmentID) > 0; + } + return false; +} + + + + + +int cItem::AddEnchantmentsFromItem(const cItem & a_Other) +{ + bool FromBook = (a_Other.m_ItemType == E_ITEM_ENCHANTED_BOOK); + + // Consider each enchantment seperately + int EnchantingCost = 0; + for (auto & Enchantment : a_Other.m_Enchantments) + { + if (CanHaveEnchantment(Enchantment.first)) + { + if (!m_Enchantments.CanAddEnchantment(Enchantment.first)) + { + // Cost of incompatible enchantments + EnchantingCost += 1; + } + else + { + EnchantingCost += AddEnchantment(Enchantment.first, Enchantment.second, FromBook); + } + } + } + return EnchantingCost; +} + + + + + //////////////////////////////////////////////////////////////////////////////// // cItems: diff --git a/src/Item.h b/src/Item.h index 8d0e9d9a0..18a1e69c0 100644 --- a/src/Item.h +++ b/src/Item.h @@ -190,9 +190,9 @@ public: void FromJson(const Json::Value & a_Value); /** Returns true if the specified item type is enchantable. - If WithBook is true, the function is used in the anvil inventory with book enchantments. + If FromBook is true, the function is used in the anvil inventory with book enchantments. So it checks the "only book enchantments" too. Example: You can only enchant a hoe with a book. */ - static bool IsEnchantable(short a_ItemType, bool a_WithBook = false); // tolua_export + static bool IsEnchantable(short a_ItemType, bool a_FromBook = false); // tolua_export /** Returns the enchantability of the item. When the item hasn't a enchantability, it will returns 0 */ int GetEnchantability(); // tolua_export @@ -201,6 +201,19 @@ public: Returns true if the item was enchanted, false if not (not enchantable / too many enchantments already). */ bool EnchantByXPLevels(int a_NumXPLevels); // tolua_export + /** Adds this specific enchantment to this item, returning the cost. + FromBook specifies whether the enchantment should be treated as coming + from a book. If true, then the cost returned uses the book values, if + false it uses the normal item multipliers. */ + int AddEnchantment(int a_EnchantmentID, unsigned int a_Level, bool a_FromBook); // tolua_export + + /** Adds the enchantments on a_Other to this item, returning the + XP cost of the transfer. */ + int AddEnchantmentsFromItem(const cItem & a_Other); // tolua_export + + /** Returns whether or not this item is allowed to have the given enchantment. Note: Does not check whether the enchantment is exclusive with the current enchantments on the item. */ + bool CanHaveEnchantment(int a_EnchantmentID); + // tolua_begin short m_ItemType; diff --git a/src/UI/SlotArea.cpp b/src/UI/SlotArea.cpp index 24c9bfb03..3729e8dc3 100644 --- a/src/UI/SlotArea.cpp +++ b/src/UI/SlotArea.cpp @@ -730,7 +730,6 @@ void cSlotAreaCrafting::UpdateRecipe(cPlayer & a_Player) cCraftingRecipe & Recipe = GetRecipeForPlayer(a_Player); cRoot::Get()->GetCraftingRecipes()->GetRecipe(a_Player, Grid, Recipe); SetSlot(0, a_Player, Recipe.GetResult()); - m_ParentWindow.SendSlot(a_Player, this, 0); } @@ -1136,7 +1135,9 @@ void cSlotAreaAnvil::UpdateResult(cPlayer & a_Player) } } - // TODO: Add enchantments. + // Add the enchantments from the sacrifice to the target + int EnchantmentCost = Input.AddEnchantmentsFromItem(SecondInput); + NeedExp += EnchantmentCost; } } @@ -1166,8 +1167,6 @@ void cSlotAreaAnvil::UpdateResult(cPlayer & a_Player) Input.m_CustomName = RepairedItemName; } - // TODO: Add enchantment exp cost. - m_MaximumCost = RepairCost + NeedExp; if (NeedExp < 0) @@ -2522,6 +2521,8 @@ void cSlotAreaTemporary::SetSlot(int a_SlotNum, cPlayer & a_Player, const cItem } itr->second[static_cast(a_SlotNum)] = a_Item; + + m_ParentWindow.SendSlot(a_Player, this, a_SlotNum); } -- cgit v1.2.3