Enchanting table shows detail on hover. Enchanting is deterministic. (#4937)
* Use lapis for enchanting, subtract correct number of levels, ClientHandle now selects from pregenerated list. Co-authored-by: Tiger Wang <ziwei.tiger@outlook.com>
This commit is contained in:
parent
be841b4769
commit
8947147c25
@ -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,6 +4447,7 @@ void cManualBindings::Bind(lua_State * tolua_S)
|
||||
tolua_endmodule(tolua_S);
|
||||
|
||||
tolua_beginmodule(tolua_S, "cItem");
|
||||
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);
|
||||
|
||||
|
@ -768,49 +768,27 @@ 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))
|
||||
// Survival players must be checked they can afford enchantment and have lapis removed:
|
||||
if (!m_Player->IsGameModeCreative())
|
||||
{
|
||||
// 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())
|
||||
{
|
||||
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;
|
||||
@ -818,10 +796,8 @@ void cClientHandle::HandleEnchantItem(UInt8 a_WindowID, UInt8 a_Enchantment)
|
||||
// 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.
|
||||
*/
|
||||
/** 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();
|
||||
@ -838,9 +814,21 @@ void cClientHandle::HandleEnchantItem(UInt8 a_WindowID, UInt8 a_Enchantment)
|
||||
// Now reduce the lapis in our stack and send it back:
|
||||
LapisStack.AddCount(-LapisRequired);
|
||||
Window->m_SlotArea->SetSlot(1, *m_Player, LapisStack);
|
||||
|
||||
SetEnchantAndBroadcast();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not creative and can't afford enchantment, so exit:
|
||||
Kick("Selected unavailable enchantment - hacked client?");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the enchanted item corresponding to our chosen option (top, middle, bottom)
|
||||
cItem EnchantedItem = Window->m_SlotArea->SelectEnchantedOption(a_Enchantment);
|
||||
|
||||
// Set the item slot to our new enchanted item:
|
||||
Window->m_SlotArea->SetSlot(0, *m_Player, EnchantedItem);
|
||||
m_Player->PermuteEnchantmentSeed();
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
21
src/Item.cpp
21
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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
// Pseudocode found at: https://minecraft.gamepedia.com/Enchanting_mechanics
|
||||
const auto Bookshelves = std::min(GetBookshelvesCount(*a_Player.GetWorld()), 15U);
|
||||
|
||||
// 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:
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user