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. --- Server/Plugins/APIDump/APIDesc.lua | 64 +++++++++++- src/Enchantments.cpp | 208 ++++++++++++++++++++++++++++++++----- src/Enchantments.h | 20 +++- src/Item.cpp | 199 ++++++++++++++++++++++++++++++++++- src/Item.h | 17 ++- src/UI/SlotArea.cpp | 9 +- 6 files changed, 480 insertions(+), 37 deletions(-) diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index 134a36d7b..9e920d429 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -2696,6 +2696,23 @@ local Hash = cCryptoHash.sha1HexString("DataToHash") }, Notes = "Returns the level of the specified enchantment stored in this object; 0 if not stored", }, + CanAddEnchantment = + { + Params = + { + { + Name = "EnchantmentNumID", + Type = "number", + }, + }, + Returns = + { + { + Type = "boolean" + }, + }, + Notes = "Returns true if the specified enchantment is not mutually exclusive with any of the enchantments stored by the object.", + }, IsEmpty = { Returns = @@ -6599,6 +6616,49 @@ These ItemGrids are available in the API and can be manipulated by the plugins, }, Notes = "Adds the specified amount to the item count. Returns self (useful for chaining).", }, + AddEnchantment = + { + Params = + { + { + Name = "Enchantment ID", + Type = "number", + }, + { + Name = "Level", + Type = "number", + }, + { + Name = "FromBook", + Type = "boolean", + }, + }, + Returns = + { + { + Type = "number", + }, + }, + Notes = "Adds the given enchantment at the given level to this item, following anvil enchantment combining rules. Returns the XP level cost of the addition. FromBook specifies whether to use the XP multiplier for books or the multiplier used for other items, if true it uses the multiplier for books.", + }, + AddEnchantmentsFromItem = + { + Params = + { + { + Name = "Additive", + Type = "cItem", + }, + }, + Returns = + { + { + Name = "LevelCost", + Type = "number", + }, + }, + Notes = "Adds the enchantments from the specified item to this item, returning the cost as if this were an anvil.", + }, Clear = { Notes = "Resets the instance to an empty item", @@ -6803,7 +6863,7 @@ These ItemGrids are available in the API and can be manipulated by the plugins, Type = "number", }, { - Name = "WithBook", + Name = "FromBook", Type = "boolean", }, }, @@ -6813,7 +6873,7 @@ These ItemGrids are available in the API and can be manipulated by the plugins, Type = "boolean", }, }, - Notes = "Returns true if the specified item type is enchantable. If WithBook 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.", + Notes = "Returns true if the specified item type is enchantable. 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.", }, IsEqual = { 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