summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/Bindings/ManualBindings.cpp30
-rw-r--r--src/ClientHandle.cpp88
-rw-r--r--src/Enchantments.cpp4
-rw-r--r--src/Enchantments.h7
-rw-r--r--src/Entities/Player.cpp23
-rw-r--r--src/Entities/Player.h8
-rw-r--r--src/Item.cpp21
-rw-r--r--src/Item.h5
-rw-r--r--src/Items/ItemFishingRod.h6
-rw-r--r--src/UI/EnchantingWindow.cpp18
-rw-r--r--src/UI/EnchantingWindow.h23
-rw-r--r--src/UI/SlotArea.cpp79
-rw-r--r--src/UI/SlotArea.h10
13 files changed, 213 insertions, 109 deletions
diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp
index 7f7cb8ea9..e62dab289 100644
--- a/src/Bindings/ManualBindings.cpp
+++ b/src/Bindings/ManualBindings.cpp
@@ -2746,6 +2746,33 @@ static int tolua_get_cItem_m_LoreTable(lua_State * tolua_S)
+static int tolua_cItem_EnchantByXPLevels(lua_State * tolua_S)
+{
+ // Check params:
+ cLuaState L(tolua_S);
+ if (
+ !L.CheckParamSelf("cItem") ||
+ !L.CheckParamNumber(2)
+ )
+ {
+ return 0;
+ }
+
+ // Get the params:
+ cItem * Self;
+ int NumXPLevels;
+ L.GetStackValue(1, Self);
+ L.GetStackValue(2, NumXPLevels);
+
+ // Call:
+ L.Push(Self->EnchantByXPLevels(NumXPLevels, GetRandomProvider()));
+ return 1;
+}
+
+
+
+
+
static int tolua_set_cItem_m_LoreTable(lua_State * tolua_S)
{
// Check params:
@@ -4420,7 +4447,8 @@ void cManualBindings::Bind(lua_State * tolua_S)
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cItem");
- tolua_variable(tolua_S, "m_LoreTable", tolua_get_cItem_m_LoreTable, tolua_set_cItem_m_LoreTable);
+ tolua_function(tolua_S, "EnchantByXPLevels", tolua_cItem_EnchantByXPLevels);
+ tolua_variable(tolua_S, "m_LoreTable", tolua_get_cItem_m_LoreTable, tolua_set_cItem_m_LoreTable);
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cItemGrid");
diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp
index 27b34eeec..848190127 100644
--- a/src/ClientHandle.cpp
+++ b/src/ClientHandle.cpp
@@ -768,79 +768,67 @@ void cClientHandle::HandleEnchantItem(UInt8 a_WindowID, UInt8 a_Enchantment)
if (a_Enchantment > 2)
{
LOGWARNING("%s attempt to crash the server with invalid enchanting selection (%u)!", GetUsername().c_str(), a_Enchantment);
- Kick("Invalid enchanting!");
+ Kick("Selected invalid enchantment - hacked client?");
return;
}
- // Bail out if something's wrong with the window
+ // Bail out if something's wrong with the window:
if (
(m_Player->GetWindow() == nullptr) ||
(m_Player->GetWindow()->GetWindowID() != a_WindowID) ||
(m_Player->GetWindow()->GetWindowType() != cWindow::wtEnchantment)
)
{
+ Kick("Enchantment with invalid window - hacked client?");
return;
}
cEnchantingWindow * Window = static_cast<cEnchantingWindow *>(m_Player->GetWindow());
- auto Item = *Window->m_SlotArea->GetSlot(0, *m_Player); // A copy of the item to be enchanted.
- short BaseEnchantmentLevel = Window->GetPropertyValue(a_Enchantment);
+ const auto BaseEnchantmentLevel = Window->GetProperty(a_Enchantment);
- if (!Item.EnchantByXPLevels(BaseEnchantmentLevel))
- {
- // Item wasn't enchantable:
- return;
- }
-
- const auto SetEnchantAndBroadcast = [this, &Item, Window]
- {
- // Set the item slot to our new enchanted item:
- Window->m_SlotArea->SetSlot(0, *m_Player, Item);
- Window->BroadcastWholeWindow();
-
- // Remove enchantment choices:
- Window->SetProperty(0, 0, *m_Player);
- Window->SetProperty(1, 0, *m_Player);
- Window->SetProperty(2, 0, *m_Player);
- };
-
- // Creative players can always enchant:
- if (m_Player->IsGameModeCreative())
+ // Survival players must be checked they can afford enchantment and have lapis removed:
+ if (!m_Player->IsGameModeCreative())
{
- SetEnchantAndBroadcast();
- return;
- }
+ const auto XpRequired = m_Player->XpForLevel(BaseEnchantmentLevel);
+ auto LapisStack = *Window->m_SlotArea->GetSlot(1, *m_Player); // A copy of the lapis stack.
+ const auto LapisRequired = a_Enchantment + 1;
- const auto XpRequired = m_Player->XpForLevel(BaseEnchantmentLevel);
- auto LapisStack = *Window->m_SlotArea->GetSlot(1, *m_Player); // A copy of the lapis stack.
- const auto LapisRequired = a_Enchantment + 1;
+ // Only allow enchantment if the player has sufficient levels and lapis to enchant:
+ if ((m_Player->GetCurrentXp() >= XpRequired) && (LapisStack.m_ItemCount >= LapisRequired))
+ {
+ /** We need to reduce the player's level by the number of lapis required.
+ However we need to keep the resulting percentage filled the same. */
- // Only allow enchantment if the player has sufficient levels and lapis to enchant:
- if ((m_Player->GetCurrentXp() >= XpRequired) && (LapisStack.m_ItemCount >= LapisRequired))
- {
- /*
- We need to reduce the player's level by the number of lapis required.
- However we need to keep the resulting percentage filled the same.
- */
+ const auto TargetLevel = m_Player->GetXpLevel() - LapisRequired;
+ const auto CurrentFillPercent = m_Player->GetXpPercentage();
- const auto TargetLevel = m_Player->GetXpLevel() - LapisRequired;
- const auto CurrentFillPercent = m_Player->GetXpPercentage();
+ // The experience to remove in order to reach the start (0% fill) of the target level.
+ const auto DeltaForLevel = -m_Player->GetCurrentXp() + m_Player->XpForLevel(TargetLevel);
- // The experience to remove in order to reach the start (0% fill) of the target level.
- const auto DeltaForLevel = -m_Player->GetCurrentXp() + m_Player->XpForLevel(TargetLevel);
+ // The experience to add to get the same fill percent.
+ const auto DeltaForPercent = CurrentFillPercent * (m_Player->XpForLevel(TargetLevel + 1) - m_Player->XpForLevel(TargetLevel));
- // The experience to add to get the same fill percent.
- const auto DeltaForPercent = CurrentFillPercent * (m_Player->XpForLevel(TargetLevel + 1) - m_Player->XpForLevel(TargetLevel));
+ // Apply the experience delta:
+ m_Player->DeltaExperience(DeltaForLevel + DeltaForPercent);
- // Apply the experience delta:
- m_Player->DeltaExperience(DeltaForLevel + DeltaForPercent);
+ // Now reduce the lapis in our stack and send it back:
+ LapisStack.AddCount(-LapisRequired);
+ Window->m_SlotArea->SetSlot(1, *m_Player, LapisStack);
+ }
+ else
+ {
+ // Not creative and can't afford enchantment, so exit:
+ Kick("Selected unavailable enchantment - hacked client?");
+ return;
+ }
+ }
- // Now reduce the lapis in our stack and send it back:
- LapisStack.AddCount(-LapisRequired);
- Window->m_SlotArea->SetSlot(1, *m_Player, LapisStack);
+ // Retrieve the enchanted item corresponding to our chosen option (top, middle, bottom)
+ cItem EnchantedItem = Window->m_SlotArea->SelectEnchantedOption(a_Enchantment);
- SetEnchantAndBroadcast();
- }
+ // Set the item slot to our new enchanted item:
+ Window->m_SlotArea->SetSlot(0, *m_Player, EnchantedItem);
+ m_Player->PermuteEnchantmentSeed();
}
diff --git a/src/Enchantments.cpp b/src/Enchantments.cpp
index 8eda97eba..e1609bf62 100644
--- a/src/Enchantments.cpp
+++ b/src/Enchantments.cpp
@@ -1170,14 +1170,14 @@ void cEnchantments::CheckEnchantmentConflictsFromVector(
-cEnchantments cEnchantments::GetRandomEnchantmentFromVector(const cWeightedEnchantments & a_Enchantments)
+cEnchantments cEnchantments::GetRandomEnchantmentFromVector(const cWeightedEnchantments & a_Enchantments, MTRand & a_Random)
{
int AllWeights = 0;
for (const auto & Enchantment: a_Enchantments)
{
AllWeights += Enchantment.m_Weight;
}
- int RandomNumber = GetRandomProvider().RandInt(AllWeights - 1);
+ int RandomNumber = a_Random.RandInt(AllWeights - 1);
for (const auto & Enchantment: a_Enchantments)
{
RandomNumber -= Enchantment.m_Weight;
diff --git a/src/Enchantments.h b/src/Enchantments.h
index 40dbee038..1f4547058 100644
--- a/src/Enchantments.h
+++ b/src/Enchantments.h
@@ -9,11 +9,11 @@
#pragma once
#include "Defines.h"
+#include "FastRandom.h"
#include "WorldStorage/EnchantmentSerializer.h"
-
// fwd: "WorldStorage/FastNBT.h"
class cFastNBTWriter;
class cParsedNBT;
@@ -27,6 +27,7 @@ typedef std::vector<cWeightedEnchantment> cWeightedEnchantments;
+
/** Class that stores item enchantments or stored-enchantments
The enchantments may be serialized to a stringspec and read back from such stringspec.
The format for the stringspec is "id=lvl;id=lvl;id=lvl...", with an optional semicolon at the end,
@@ -139,8 +140,8 @@ public:
/** Check enchantment conflicts from enchantments from the vector */
static void CheckEnchantmentConflictsFromVector(cWeightedEnchantments & a_Enchantments, const cEnchantments & a_FirstEnchantment);
- /** Gets random enchantment from Vector and returns it */
- static cEnchantments GetRandomEnchantmentFromVector(const cWeightedEnchantments & a_Enchantments);
+ /** Gets random enchantment from Vector and returns it, with randomness derived from the provided PRNG. */
+ static cEnchantments GetRandomEnchantmentFromVector(const cWeightedEnchantments & a_Enchantments, MTRand & a_Random);
/** Selects one enchantment from a Vector using cNoise. Mostly used for generators.
Uses the enchantments' weights for the random distribution.
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index bbe237d39..d7455f371 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -151,6 +151,8 @@ cPlayer::cPlayer(const cClientHandlePtr & a_Client, const AString & a_PlayerName
SetWorld(World); // Use default world
+ m_EnchantmentSeed = GetRandomProvider().RandInt<unsigned int>(); // Use a random number to seed the enchantment generator
+
FLOGD("Player \"{0}\" is connecting for the first time, spawning at default world spawn {1:.2f}",
a_PlayerName, GetPosition()
);
@@ -1861,6 +1863,25 @@ void cPlayer::SetVisible(bool a_bVisible)
+MTRand cPlayer::GetEnchantmentRandomProvider()
+{
+ return m_EnchantmentSeed;
+}
+
+
+
+
+
+void cPlayer::PermuteEnchantmentSeed()
+{
+ // Get a new random integer and save that as the seed:
+ m_EnchantmentSeed = GetRandomProvider().RandInt<unsigned int>();
+}
+
+
+
+
+
bool cPlayer::HasPermission(const AString & a_Permission)
{
if (a_Permission.empty())
@@ -2277,6 +2298,7 @@ bool cPlayer::LoadFromFile(const AString & a_FileName, cWorldPtr & a_World)
m_LifetimeTotalXp = root.get("xpTotal", 0).asInt();
m_CurrentXp = root.get("xpCurrent", 0).asInt();
m_IsFlying = root.get("isflying", 0).asBool();
+ m_EnchantmentSeed = root.get("enchantmentSeed", GetRandomProvider().RandInt<unsigned int>()).asUInt();
Json::Value & JSON_KnownItems = root["knownItems"];
for (UInt32 i = 0; i < JSON_KnownItems.size(); i++)
@@ -2439,6 +2461,7 @@ bool cPlayer::SaveToDisk()
root["SpawnY"] = GetLastBedPos().y;
root["SpawnZ"] = GetLastBedPos().z;
root["SpawnWorld"] = m_SpawnWorld->GetName();
+ root["enchantmentSeed"] = m_EnchantmentSeed;
if (m_World != nullptr)
{
diff --git a/src/Entities/Player.h b/src/Entities/Player.h
index ba219a84d..568929f44 100644
--- a/src/Entities/Player.h
+++ b/src/Entities/Player.h
@@ -263,6 +263,13 @@ public:
// tolua_end
+ /** Get a copy of the PRNG for enchanting related generation, don't use this for other purposes.
+ The PRNG's state is initialised with an internal seed, such that until PermuteEnchantmentSeed is called, this function returns the same PRNG. */
+ MTRand GetEnchantmentRandomProvider();
+
+ /** Permute the seed for enchanting related PRNGs, don't use this for other purposes. */
+ void PermuteEnchantmentSeed();
+
/** Returns the SharedPtr to client handle associated with the player. */
cClientHandlePtr GetClientHandlePtr(void) const { return m_ClientHandle; }
@@ -718,6 +725,7 @@ protected:
/** Player Xp level */
int m_LifetimeTotalXp;
int m_CurrentXp;
+ unsigned int m_EnchantmentSeed;
// flag saying we need to send a xp update to client
bool m_bDirtyExperience;
diff --git a/src/Item.cpp b/src/Item.cpp
index ca4210c75..60991d37a 100644
--- a/src/Item.cpp
+++ b/src/Item.cpp
@@ -429,7 +429,7 @@ int cItem::GetEnchantability()
-bool cItem::EnchantByXPLevels(int a_NumXPLevels)
+bool cItem::EnchantByXPLevels(int a_NumXPLevels, MTRand & a_Random)
{
if (!cItem::IsEnchantable(m_ItemType))
{
@@ -442,9 +442,8 @@ bool cItem::EnchantByXPLevels(int a_NumXPLevels)
return false;
}
- auto & Random = GetRandomProvider();
- int ModifiedEnchantmentLevel = a_NumXPLevels + Random.RandInt(Enchantability / 4) + Random.RandInt(Enchantability / 4) + 1;
- float RandomBonus = 1.0F + (Random.RandReal() + Random.RandReal() - 1.0F) * 0.15F;
+ int ModifiedEnchantmentLevel = a_NumXPLevels + a_Random.RandInt(Enchantability / 4) + a_Random.RandInt(Enchantability / 4) + 1;
+ float RandomBonus = 1.0F + (a_Random.RandReal() + a_Random.RandReal() - 1.0F) * 0.15F;
int FinalEnchantmentLevel = static_cast<int>(ModifiedEnchantmentLevel * RandomBonus + 0.5F);
cWeightedEnchantments Enchantments;
@@ -455,7 +454,7 @@ bool cItem::EnchantByXPLevels(int a_NumXPLevels)
m_ItemType = E_ITEM_ENCHANTED_BOOK;
}
- cEnchantments Enchantment1 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments);
+ cEnchantments Enchantment1 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments, a_Random);
m_Enchantments.AddFromString(Enchantment1.ToString());
cEnchantments::RemoveEnchantmentWeightFromVector(Enchantments, Enchantment1);
@@ -465,12 +464,12 @@ bool cItem::EnchantByXPLevels(int a_NumXPLevels)
// Next Enchantment (Second)
float NewEnchantmentLevel = a_NumXPLevels / 2.0f;
float SecondEnchantmentChance = (NewEnchantmentLevel + 1) / 50.0f;
- if (Enchantments.empty() || !Random.RandBool(SecondEnchantmentChance))
+ if (Enchantments.empty() || !a_Random.RandBool(SecondEnchantmentChance))
{
return true;
}
- cEnchantments Enchantment2 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments);
+ cEnchantments Enchantment2 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments, a_Random);
m_Enchantments.AddFromString(Enchantment2.ToString());
cEnchantments::RemoveEnchantmentWeightFromVector(Enchantments, Enchantment2);
@@ -480,12 +479,12 @@ bool cItem::EnchantByXPLevels(int a_NumXPLevels)
// Next Enchantment (Third)
NewEnchantmentLevel = NewEnchantmentLevel / 2.0f;
float ThirdEnchantmentChance = (NewEnchantmentLevel + 1) / 50.0f;
- if (Enchantments.empty() || !Random.RandBool(ThirdEnchantmentChance))
+ if (Enchantments.empty() || !a_Random.RandBool(ThirdEnchantmentChance))
{
return true;
}
- cEnchantments Enchantment3 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments);
+ cEnchantments Enchantment3 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments, a_Random);
m_Enchantments.AddFromString(Enchantment3.ToString());
cEnchantments::RemoveEnchantmentWeightFromVector(Enchantments, Enchantment3);
@@ -495,11 +494,11 @@ bool cItem::EnchantByXPLevels(int a_NumXPLevels)
// Next Enchantment (Fourth)
NewEnchantmentLevel = NewEnchantmentLevel / 2.0f;
float FourthEnchantmentChance = (NewEnchantmentLevel + 1) / 50.0f;
- if (Enchantments.empty() || !Random.RandBool(FourthEnchantmentChance))
+ if (Enchantments.empty() || !a_Random.RandBool(FourthEnchantmentChance))
{
return true;
}
- cEnchantments Enchantment4 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments);
+ cEnchantments Enchantment4 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments, a_Random);
m_Enchantments.AddFromString(Enchantment4.ToString());
return true;
diff --git a/src/Item.h b/src/Item.h
index d3f853170..600c8b2f1 100644
--- a/src/Item.h
+++ b/src/Item.h
@@ -141,8 +141,9 @@ public:
int GetEnchantability(); // tolua_export
/** Randomly enchants the item using the specified number of XP levels.
- Returns true if the item was enchanted, false if not (not enchantable / too many enchantments already). */
- bool EnchantByXPLevels(int a_NumXPLevels); // tolua_export
+ Returns true if the item was enchanted, false if not (not enchantable / too many enchantments already).
+ Randomness is derived from the provided PRNG. */
+ bool EnchantByXPLevels(int a_NumXPLevels, MTRand & a_Random); // Exported in ManualBindings.cpp
/** Adds this specific enchantment to this item, returning the cost.
FromBook specifies whether the enchantment should be treated as coming
diff --git a/src/Items/ItemFishingRod.h b/src/Items/ItemFishingRod.h
index 5878890ef..85688c5c1 100644
--- a/src/Items/ItemFishingRod.h
+++ b/src/Items/ItemFishingRod.h
@@ -175,21 +175,21 @@ public:
case 0:
{
cItem Bow(E_ITEM_BOW, 1, Random.RandInt<short>(50));
- Bow.EnchantByXPLevels(Random.RandInt(22, 30));
+ Bow.EnchantByXPLevels(Random.RandInt(22, 30), GetRandomProvider());
Drops.Add(Bow);
break;
}
case 1:
{
cItem Book(E_ITEM_BOOK);
- Book.EnchantByXPLevels(30);
+ Book.EnchantByXPLevels(30, GetRandomProvider());
Drops.Add(Book);
break;
}
case 2:
{
cItem Rod(E_ITEM_FISHING_ROD, 1, Random.RandInt<short>(50));
- Rod.EnchantByXPLevels(Random.RandInt(22, 30));
+ Rod.EnchantByXPLevels(Random.RandInt(22, 30), GetRandomProvider());
Drops.Add(Rod);
break;
}
diff --git a/src/UI/EnchantingWindow.cpp b/src/UI/EnchantingWindow.cpp
index 4ce4bebb3..15d27fc63 100644
--- a/src/UI/EnchantingWindow.cpp
+++ b/src/UI/EnchantingWindow.cpp
@@ -28,13 +28,12 @@ cEnchantingWindow::cEnchantingWindow(Vector3i a_BlockPos, const AString & a_Titl
void cEnchantingWindow::SetProperty(short a_Property, short a_Value, cPlayer & a_Player)
{
- if ((a_Property < 0) || (static_cast<size_t>(a_Property) >= ARRAYCOUNT(m_PropertyValue)))
+ ASSERT(a_Property >= 0);
+ if (static_cast<size_t>(a_Property) < m_PropertyValue.size())
{
- ASSERT(!"a_Property is invalid");
- return;
+ m_PropertyValue[a_Property] = a_Value;
}
- m_PropertyValue[a_Property] = a_Value;
Super::SetProperty(a_Property, a_Value, a_Player);
}
@@ -44,13 +43,12 @@ void cEnchantingWindow::SetProperty(short a_Property, short a_Value, cPlayer & a
void cEnchantingWindow::SetProperty(short a_Property, short a_Value)
{
- if ((a_Property < 0) || (static_cast<size_t>(a_Property) >= ARRAYCOUNT(m_PropertyValue)))
+ ASSERT(a_Property >= 0);
+ if (static_cast<size_t>(a_Property) < m_PropertyValue.size())
{
- ASSERT(!"a_Property is invalid");
- return;
+ m_PropertyValue[a_Property] = a_Value;
}
- m_PropertyValue[a_Property] = a_Value;
Super::SetProperty(a_Property, a_Value);
}
@@ -58,9 +56,9 @@ void cEnchantingWindow::SetProperty(short a_Property, short a_Value)
-short cEnchantingWindow::GetPropertyValue(short a_Property)
+short cEnchantingWindow::GetProperty(short a_Property)
{
- if ((a_Property < 0) || (static_cast<size_t>(a_Property) >= ARRAYCOUNT(m_PropertyValue)))
+ if ((a_Property < 0) || (static_cast<size_t>(a_Property) >= m_PropertyValue.size()))
{
ASSERT(!"a_Property is invalid");
return 0;
diff --git a/src/UI/EnchantingWindow.h b/src/UI/EnchantingWindow.h
index 4de5ed00c..2b32f078a 100644
--- a/src/UI/EnchantingWindow.h
+++ b/src/UI/EnchantingWindow.h
@@ -15,6 +15,12 @@
+class cSlotAreaEnchanting;
+
+
+
+
+
class cEnchantingWindow:
public cWindow
{
@@ -24,22 +30,23 @@ public:
cEnchantingWindow(Vector3i a_BlockPos, const AString & a_Title);
+ /** Sends enchantment properties to the client.
+ If the property represents a level requirement, stores it for later GetProperty retrieval. */
virtual void SetProperty(short a_Property, short a_Value, cPlayer & a_Player) override;
+ /** Sends enchantment properties to the client.
+ If the property represents a level requirement, stores it for later GetProperty retrieval. */
virtual void SetProperty(short a_Property, short a_Value) override;
- /** Return the value of a property */
- short GetPropertyValue(short a_Property);
+ /** Return the level requirement of the given enchantment slot. */
+ short GetProperty(short a_Property);
virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override;
- cSlotArea * m_SlotArea;
+ cSlotAreaEnchanting * m_SlotArea;
protected:
- short m_PropertyValue[3];
+
+ std::array<short, 3> m_PropertyValue;
Vector3i m_BlockPos;
};
-
-
-
-
diff --git a/src/UI/SlotArea.cpp b/src/UI/SlotArea.cpp
index 1450294a4..bb597b2c9 100644
--- a/src/UI/SlotArea.cpp
+++ b/src/UI/SlotArea.cpp
@@ -1678,25 +1678,59 @@ void cSlotAreaEnchanting::UpdateResult(cPlayer & a_Player)
{
cItem Item = *GetSlot(0, a_Player);
- if (cItem::IsEnchantable(Item.m_ItemType) && Item.m_Enchantments.IsEmpty())
+ if (!cItem::IsEnchantable(Item.m_ItemType) || !Item.m_Enchantments.IsEmpty())
{
- int Bookshelves = std::min(GetBookshelvesCount(*a_Player.GetWorld()), 15);
+ return;
+ }
- auto & Random = GetRandomProvider();
- int Base = (Random.RandInt(1, 8) + (Bookshelves / 2) + Random.RandInt(0, Bookshelves));
- int TopSlot = std::max(Base / 3, 1);
- int MiddleSlot = (Base * 2) / 3 + 1;
- int BottomSlot = std::max(Base, Bookshelves * 2);
+ // Pseudocode found at: https://minecraft.gamepedia.com/Enchanting_mechanics
+ const auto Bookshelves = std::min(GetBookshelvesCount(*a_Player.GetWorld()), 15U);
- m_ParentWindow.SetProperty(0, static_cast<short>(TopSlot), a_Player);
- m_ParentWindow.SetProperty(1, static_cast<short>(MiddleSlot), a_Player);
- m_ParentWindow.SetProperty(2, static_cast<short>(BottomSlot), a_Player);
- }
- else
+ // A PRNG initialised using the player's enchantment seed.
+ auto Random = a_Player.GetEnchantmentRandomProvider();
+
+ // Calculate the levels for the offered enchantment options:
+ const auto Base = (Random.RandInt(1U, 8U) + (Bookshelves / 2) + Random.RandInt(0U, Bookshelves));
+ const std::array<unsigned int, 3> OptionLevels
{
- m_ParentWindow.SetProperty(0, 0, a_Player);
- m_ParentWindow.SetProperty(1, 0, a_Player);
- m_ParentWindow.SetProperty(2, 0, a_Player);
+ std::max(Base / 3, 1U),
+ (Base * 2) / 3 + 1,
+ std::max(Base, Bookshelves * 2)
+ };
+
+ // Properties set according to: https://wiki.vg/Protocol#Window_Property
+ // Fake a "seed" for the client to draw Standard Galactic Alphabet glyphs:
+ m_ParentWindow.SetProperty(3, Random.RandInt<short>(), a_Player);
+
+ // Calculate an enchanting possibility for each option (top, middle and bottom) and send details to window:
+ for (short i = 0; i != OptionLevels.size(); i++)
+ {
+ // A copy of the item.
+ cItem EnchantedItem = Item.CopyOne();
+
+ // Enchant based on the number of levels:
+ EnchantedItem.EnchantByXPLevels(OptionLevels[i], Random);
+
+ LOGD("Generated enchanted item %d with enchantments: %s", i, EnchantedItem.m_Enchantments.ToString());
+
+ // Send the level requirement for the enchantment option:
+ m_ParentWindow.SetProperty(i, static_cast<short>(OptionLevels[i]), a_Player);
+
+ // Get the first enchantment ID, which must exist:
+ ASSERT(EnchantedItem.m_Enchantments.begin() != EnchantedItem.m_Enchantments.end());
+ const short EnchantmentID = static_cast<short>(EnchantedItem.m_Enchantments.begin()->first);
+
+ // Send the enchantment ID of the first enchantment on our item:
+ m_ParentWindow.SetProperty(4 + i, EnchantmentID, a_Player);
+
+ const short EnchantmentLevel = static_cast<short>(EnchantedItem.m_Enchantments.GetLevel(EnchantmentID));
+ ASSERT(EnchantmentLevel > 0);
+
+ // Send the level for the first enchantment on our item:
+ m_ParentWindow.SetProperty(7 + i, EnchantmentLevel, a_Player);
+
+ // Store the item we've enchanted as an option to be retrieved later:
+ m_EnchantedItemOptions[i] = std::move(EnchantedItem);
}
}
@@ -1704,9 +1738,8 @@ void cSlotAreaEnchanting::UpdateResult(cPlayer & a_Player)
-int cSlotAreaEnchanting::GetBookshelvesCount(cWorld & a_World)
+unsigned cSlotAreaEnchanting::GetBookshelvesCount(cWorld & a_World)
{
- int Bookshelves = 0;
cBlockArea Area;
Area.Read(a_World, m_BlockPos - Vector3i(2, 0, 2), m_BlockPos + Vector3i(2, 1, 2));
@@ -1751,6 +1784,8 @@ int cSlotAreaEnchanting::GetBookshelvesCount(cWorld & a_World)
{ 1, 1, 0, 1, 1, 1 }, // Bookcase at {1, 1, 0}, air at {1, 1, 1}
};
+ unsigned Bookshelves = 0;
+
for (size_t i = 0; i < ARRAYCOUNT(CheckCoords); i++)
{
if (
@@ -1769,6 +1804,16 @@ int cSlotAreaEnchanting::GetBookshelvesCount(cWorld & a_World)
+cItem cSlotAreaEnchanting::SelectEnchantedOption(size_t a_EnchantOption)
+{
+ ASSERT(a_EnchantOption < m_EnchantedItemOptions.size());
+ return std::move(m_EnchantedItemOptions[a_EnchantOption]);
+}
+
+
+
+
+
////////////////////////////////////////////////////////////////////////////////
// cSlotAreaEnderChest:
diff --git a/src/UI/SlotArea.h b/src/UI/SlotArea.h
index d363a72e6..acf8c404b 100644
--- a/src/UI/SlotArea.h
+++ b/src/UI/SlotArea.h
@@ -397,14 +397,20 @@ public:
virtual void OnPlayerAdded (cPlayer & a_Player) override;
virtual void OnPlayerRemoved(cPlayer & a_Player) override;
- /* Get the count of bookshelves who stand in the near of the enchanting table */
- int GetBookshelvesCount(cWorld & a_World);
+ /* Get the number of bookshelves which are near the enchanting table */
+ unsigned GetBookshelvesCount(cWorld & a_World);
+
+ /* Return the enchanted item matching the chosen option (0, 1, 2)
+ Ownership of the cItem is transferred to the caller. */
+ cItem SelectEnchantedOption(size_t a_EnchantOption);
protected:
+
/** Handles a click in the item slot. */
void UpdateResult(cPlayer & a_Player);
Vector3i m_BlockPos;
+ std::array<cItem, 3> m_EnchantedItemOptions;
};