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)
|
static int tolua_set_cItem_m_LoreTable(lua_State * tolua_S)
|
||||||
{
|
{
|
||||||
// Check params:
|
// Check params:
|
||||||
@ -4420,7 +4447,8 @@ void cManualBindings::Bind(lua_State * tolua_S)
|
|||||||
tolua_endmodule(tolua_S);
|
tolua_endmodule(tolua_S);
|
||||||
|
|
||||||
tolua_beginmodule(tolua_S, "cItem");
|
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_endmodule(tolua_S);
|
||||||
|
|
||||||
tolua_beginmodule(tolua_S, "cItemGrid");
|
tolua_beginmodule(tolua_S, "cItemGrid");
|
||||||
|
@ -768,79 +768,67 @@ void cClientHandle::HandleEnchantItem(UInt8 a_WindowID, UInt8 a_Enchantment)
|
|||||||
if (a_Enchantment > 2)
|
if (a_Enchantment > 2)
|
||||||
{
|
{
|
||||||
LOGWARNING("%s attempt to crash the server with invalid enchanting selection (%u)!", GetUsername().c_str(), a_Enchantment);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bail out if something's wrong with the window
|
// Bail out if something's wrong with the window:
|
||||||
if (
|
if (
|
||||||
(m_Player->GetWindow() == nullptr) ||
|
(m_Player->GetWindow() == nullptr) ||
|
||||||
(m_Player->GetWindow()->GetWindowID() != a_WindowID) ||
|
(m_Player->GetWindow()->GetWindowID() != a_WindowID) ||
|
||||||
(m_Player->GetWindow()->GetWindowType() != cWindow::wtEnchantment)
|
(m_Player->GetWindow()->GetWindowType() != cWindow::wtEnchantment)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
Kick("Enchantment with invalid window - hacked client?");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cEnchantingWindow * Window = static_cast<cEnchantingWindow *>(m_Player->GetWindow());
|
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.
|
const auto BaseEnchantmentLevel = Window->GetProperty(a_Enchantment);
|
||||||
short BaseEnchantmentLevel = Window->GetPropertyValue(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:
|
const auto XpRequired = m_Player->XpForLevel(BaseEnchantmentLevel);
|
||||||
return;
|
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. */
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto SetEnchantAndBroadcast = [this, &Item, Window]
|
// 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, Item);
|
|
||||||
Window->BroadcastWholeWindow();
|
|
||||||
|
|
||||||
// Remove enchantment choices:
|
// Set the item slot to our new enchanted item:
|
||||||
Window->SetProperty(0, 0, *m_Player);
|
Window->m_SlotArea->SetSlot(0, *m_Player, EnchantedItem);
|
||||||
Window->SetProperty(1, 0, *m_Player);
|
m_Player->PermuteEnchantmentSeed();
|
||||||
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;
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
|
|
||||||
// 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));
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
SetEnchantAndBroadcast();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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;
|
int AllWeights = 0;
|
||||||
for (const auto & Enchantment: a_Enchantments)
|
for (const auto & Enchantment: a_Enchantments)
|
||||||
{
|
{
|
||||||
AllWeights += Enchantment.m_Weight;
|
AllWeights += Enchantment.m_Weight;
|
||||||
}
|
}
|
||||||
int RandomNumber = GetRandomProvider().RandInt(AllWeights - 1);
|
int RandomNumber = a_Random.RandInt(AllWeights - 1);
|
||||||
for (const auto & Enchantment: a_Enchantments)
|
for (const auto & Enchantment: a_Enchantments)
|
||||||
{
|
{
|
||||||
RandomNumber -= Enchantment.m_Weight;
|
RandomNumber -= Enchantment.m_Weight;
|
||||||
|
@ -9,11 +9,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Defines.h"
|
#include "Defines.h"
|
||||||
|
#include "FastRandom.h"
|
||||||
#include "WorldStorage/EnchantmentSerializer.h"
|
#include "WorldStorage/EnchantmentSerializer.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// fwd: "WorldStorage/FastNBT.h"
|
// fwd: "WorldStorage/FastNBT.h"
|
||||||
class cFastNBTWriter;
|
class cFastNBTWriter;
|
||||||
class cParsedNBT;
|
class cParsedNBT;
|
||||||
@ -27,6 +27,7 @@ typedef std::vector<cWeightedEnchantment> cWeightedEnchantments;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Class that stores item enchantments or stored-enchantments
|
/** Class that stores item enchantments or stored-enchantments
|
||||||
The enchantments may be serialized to a stringspec and read back from such stringspec.
|
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,
|
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 */
|
/** Check enchantment conflicts from enchantments from the vector */
|
||||||
static void CheckEnchantmentConflictsFromVector(cWeightedEnchantments & a_Enchantments, const cEnchantments & a_FirstEnchantment);
|
static void CheckEnchantmentConflictsFromVector(cWeightedEnchantments & a_Enchantments, const cEnchantments & a_FirstEnchantment);
|
||||||
|
|
||||||
/** Gets random enchantment from Vector and returns it */
|
/** Gets random enchantment from Vector and returns it, with randomness derived from the provided PRNG. */
|
||||||
static cEnchantments GetRandomEnchantmentFromVector(const cWeightedEnchantments & a_Enchantments);
|
static cEnchantments GetRandomEnchantmentFromVector(const cWeightedEnchantments & a_Enchantments, MTRand & a_Random);
|
||||||
|
|
||||||
/** Selects one enchantment from a Vector using cNoise. Mostly used for generators.
|
/** Selects one enchantment from a Vector using cNoise. Mostly used for generators.
|
||||||
Uses the enchantments' weights for the random distribution.
|
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
|
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}",
|
FLOGD("Player \"{0}\" is connecting for the first time, spawning at default world spawn {1:.2f}",
|
||||||
a_PlayerName, GetPosition()
|
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)
|
bool cPlayer::HasPermission(const AString & a_Permission)
|
||||||
{
|
{
|
||||||
if (a_Permission.empty())
|
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_LifetimeTotalXp = root.get("xpTotal", 0).asInt();
|
||||||
m_CurrentXp = root.get("xpCurrent", 0).asInt();
|
m_CurrentXp = root.get("xpCurrent", 0).asInt();
|
||||||
m_IsFlying = root.get("isflying", 0).asBool();
|
m_IsFlying = root.get("isflying", 0).asBool();
|
||||||
|
m_EnchantmentSeed = root.get("enchantmentSeed", GetRandomProvider().RandInt<unsigned int>()).asUInt();
|
||||||
|
|
||||||
Json::Value & JSON_KnownItems = root["knownItems"];
|
Json::Value & JSON_KnownItems = root["knownItems"];
|
||||||
for (UInt32 i = 0; i < JSON_KnownItems.size(); i++)
|
for (UInt32 i = 0; i < JSON_KnownItems.size(); i++)
|
||||||
@ -2439,6 +2461,7 @@ bool cPlayer::SaveToDisk()
|
|||||||
root["SpawnY"] = GetLastBedPos().y;
|
root["SpawnY"] = GetLastBedPos().y;
|
||||||
root["SpawnZ"] = GetLastBedPos().z;
|
root["SpawnZ"] = GetLastBedPos().z;
|
||||||
root["SpawnWorld"] = m_SpawnWorld->GetName();
|
root["SpawnWorld"] = m_SpawnWorld->GetName();
|
||||||
|
root["enchantmentSeed"] = m_EnchantmentSeed;
|
||||||
|
|
||||||
if (m_World != nullptr)
|
if (m_World != nullptr)
|
||||||
{
|
{
|
||||||
|
@ -263,6 +263,13 @@ public:
|
|||||||
|
|
||||||
// tolua_end
|
// 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. */
|
/** Returns the SharedPtr to client handle associated with the player. */
|
||||||
cClientHandlePtr GetClientHandlePtr(void) const { return m_ClientHandle; }
|
cClientHandlePtr GetClientHandlePtr(void) const { return m_ClientHandle; }
|
||||||
|
|
||||||
@ -718,6 +725,7 @@ protected:
|
|||||||
/** Player Xp level */
|
/** Player Xp level */
|
||||||
int m_LifetimeTotalXp;
|
int m_LifetimeTotalXp;
|
||||||
int m_CurrentXp;
|
int m_CurrentXp;
|
||||||
|
unsigned int m_EnchantmentSeed;
|
||||||
|
|
||||||
// flag saying we need to send a xp update to client
|
// flag saying we need to send a xp update to client
|
||||||
bool m_bDirtyExperience;
|
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))
|
if (!cItem::IsEnchantable(m_ItemType))
|
||||||
{
|
{
|
||||||
@ -442,9 +442,8 @@ bool cItem::EnchantByXPLevels(int a_NumXPLevels)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & Random = GetRandomProvider();
|
int ModifiedEnchantmentLevel = a_NumXPLevels + a_Random.RandInt(Enchantability / 4) + a_Random.RandInt(Enchantability / 4) + 1;
|
||||||
int ModifiedEnchantmentLevel = a_NumXPLevels + Random.RandInt(Enchantability / 4) + Random.RandInt(Enchantability / 4) + 1;
|
float RandomBonus = 1.0F + (a_Random.RandReal() + a_Random.RandReal() - 1.0F) * 0.15F;
|
||||||
float RandomBonus = 1.0F + (Random.RandReal() + Random.RandReal() - 1.0F) * 0.15F;
|
|
||||||
int FinalEnchantmentLevel = static_cast<int>(ModifiedEnchantmentLevel * RandomBonus + 0.5F);
|
int FinalEnchantmentLevel = static_cast<int>(ModifiedEnchantmentLevel * RandomBonus + 0.5F);
|
||||||
|
|
||||||
cWeightedEnchantments Enchantments;
|
cWeightedEnchantments Enchantments;
|
||||||
@ -455,7 +454,7 @@ bool cItem::EnchantByXPLevels(int a_NumXPLevels)
|
|||||||
m_ItemType = E_ITEM_ENCHANTED_BOOK;
|
m_ItemType = E_ITEM_ENCHANTED_BOOK;
|
||||||
}
|
}
|
||||||
|
|
||||||
cEnchantments Enchantment1 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments);
|
cEnchantments Enchantment1 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments, a_Random);
|
||||||
m_Enchantments.AddFromString(Enchantment1.ToString());
|
m_Enchantments.AddFromString(Enchantment1.ToString());
|
||||||
cEnchantments::RemoveEnchantmentWeightFromVector(Enchantments, Enchantment1);
|
cEnchantments::RemoveEnchantmentWeightFromVector(Enchantments, Enchantment1);
|
||||||
|
|
||||||
@ -465,12 +464,12 @@ bool cItem::EnchantByXPLevels(int a_NumXPLevels)
|
|||||||
// Next Enchantment (Second)
|
// Next Enchantment (Second)
|
||||||
float NewEnchantmentLevel = a_NumXPLevels / 2.0f;
|
float NewEnchantmentLevel = a_NumXPLevels / 2.0f;
|
||||||
float SecondEnchantmentChance = (NewEnchantmentLevel + 1) / 50.0f;
|
float SecondEnchantmentChance = (NewEnchantmentLevel + 1) / 50.0f;
|
||||||
if (Enchantments.empty() || !Random.RandBool(SecondEnchantmentChance))
|
if (Enchantments.empty() || !a_Random.RandBool(SecondEnchantmentChance))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cEnchantments Enchantment2 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments);
|
cEnchantments Enchantment2 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments, a_Random);
|
||||||
m_Enchantments.AddFromString(Enchantment2.ToString());
|
m_Enchantments.AddFromString(Enchantment2.ToString());
|
||||||
cEnchantments::RemoveEnchantmentWeightFromVector(Enchantments, Enchantment2);
|
cEnchantments::RemoveEnchantmentWeightFromVector(Enchantments, Enchantment2);
|
||||||
|
|
||||||
@ -480,12 +479,12 @@ bool cItem::EnchantByXPLevels(int a_NumXPLevels)
|
|||||||
// Next Enchantment (Third)
|
// Next Enchantment (Third)
|
||||||
NewEnchantmentLevel = NewEnchantmentLevel / 2.0f;
|
NewEnchantmentLevel = NewEnchantmentLevel / 2.0f;
|
||||||
float ThirdEnchantmentChance = (NewEnchantmentLevel + 1) / 50.0f;
|
float ThirdEnchantmentChance = (NewEnchantmentLevel + 1) / 50.0f;
|
||||||
if (Enchantments.empty() || !Random.RandBool(ThirdEnchantmentChance))
|
if (Enchantments.empty() || !a_Random.RandBool(ThirdEnchantmentChance))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cEnchantments Enchantment3 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments);
|
cEnchantments Enchantment3 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments, a_Random);
|
||||||
m_Enchantments.AddFromString(Enchantment3.ToString());
|
m_Enchantments.AddFromString(Enchantment3.ToString());
|
||||||
cEnchantments::RemoveEnchantmentWeightFromVector(Enchantments, Enchantment3);
|
cEnchantments::RemoveEnchantmentWeightFromVector(Enchantments, Enchantment3);
|
||||||
|
|
||||||
@ -495,11 +494,11 @@ bool cItem::EnchantByXPLevels(int a_NumXPLevels)
|
|||||||
// Next Enchantment (Fourth)
|
// Next Enchantment (Fourth)
|
||||||
NewEnchantmentLevel = NewEnchantmentLevel / 2.0f;
|
NewEnchantmentLevel = NewEnchantmentLevel / 2.0f;
|
||||||
float FourthEnchantmentChance = (NewEnchantmentLevel + 1) / 50.0f;
|
float FourthEnchantmentChance = (NewEnchantmentLevel + 1) / 50.0f;
|
||||||
if (Enchantments.empty() || !Random.RandBool(FourthEnchantmentChance))
|
if (Enchantments.empty() || !a_Random.RandBool(FourthEnchantmentChance))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
cEnchantments Enchantment4 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments);
|
cEnchantments Enchantment4 = cEnchantments::GetRandomEnchantmentFromVector(Enchantments, a_Random);
|
||||||
m_Enchantments.AddFromString(Enchantment4.ToString());
|
m_Enchantments.AddFromString(Enchantment4.ToString());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -141,8 +141,9 @@ public:
|
|||||||
int GetEnchantability(); // tolua_export
|
int GetEnchantability(); // tolua_export
|
||||||
|
|
||||||
/** Randomly enchants the item using the specified number of XP levels.
|
/** 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). */
|
Returns true if the item was enchanted, false if not (not enchantable / too many enchantments already).
|
||||||
bool EnchantByXPLevels(int a_NumXPLevels); // tolua_export
|
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.
|
/** Adds this specific enchantment to this item, returning the cost.
|
||||||
FromBook specifies whether the enchantment should be treated as coming
|
FromBook specifies whether the enchantment should be treated as coming
|
||||||
|
@ -175,21 +175,21 @@ public:
|
|||||||
case 0:
|
case 0:
|
||||||
{
|
{
|
||||||
cItem Bow(E_ITEM_BOW, 1, Random.RandInt<short>(50));
|
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);
|
Drops.Add(Bow);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
{
|
{
|
||||||
cItem Book(E_ITEM_BOOK);
|
cItem Book(E_ITEM_BOOK);
|
||||||
Book.EnchantByXPLevels(30);
|
Book.EnchantByXPLevels(30, GetRandomProvider());
|
||||||
Drops.Add(Book);
|
Drops.Add(Book);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 2:
|
case 2:
|
||||||
{
|
{
|
||||||
cItem Rod(E_ITEM_FISHING_ROD, 1, Random.RandInt<short>(50));
|
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);
|
Drops.Add(Rod);
|
||||||
break;
|
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)
|
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");
|
m_PropertyValue[a_Property] = a_Value;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_PropertyValue[a_Property] = a_Value;
|
|
||||||
Super::SetProperty(a_Property, a_Value, a_Player);
|
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)
|
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");
|
m_PropertyValue[a_Property] = a_Value;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_PropertyValue[a_Property] = a_Value;
|
|
||||||
Super::SetProperty(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");
|
ASSERT(!"a_Property is invalid");
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -15,6 +15,12 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class cSlotAreaEnchanting;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class cEnchantingWindow:
|
class cEnchantingWindow:
|
||||||
public cWindow
|
public cWindow
|
||||||
{
|
{
|
||||||
@ -24,22 +30,23 @@ public:
|
|||||||
|
|
||||||
cEnchantingWindow(Vector3i a_BlockPos, const AString & a_Title);
|
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;
|
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;
|
virtual void SetProperty(short a_Property, short a_Value) override;
|
||||||
|
|
||||||
/** Return the value of a property */
|
/** Return the level requirement of the given enchantment slot. */
|
||||||
short GetPropertyValue(short a_Property);
|
short GetProperty(short a_Property);
|
||||||
|
|
||||||
virtual void DistributeStack(cItem & a_ItemStack, int a_Slot, cPlayer & a_Player, cSlotArea * a_ClickedArea, bool a_ShouldApply) override;
|
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:
|
protected:
|
||||||
short m_PropertyValue[3];
|
|
||||||
|
std::array<short, 3> m_PropertyValue;
|
||||||
Vector3i m_BlockPos;
|
Vector3i m_BlockPos;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1678,25 +1678,59 @@ void cSlotAreaEnchanting::UpdateResult(cPlayer & a_Player)
|
|||||||
{
|
{
|
||||||
cItem Item = *GetSlot(0, 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);
|
|
||||||
|
|
||||||
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
|
|
||||||
|
// 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);
|
std::max(Base / 3, 1U),
|
||||||
m_ParentWindow.SetProperty(1, 0, a_Player);
|
(Base * 2) / 3 + 1,
|
||||||
m_ParentWindow.SetProperty(2, 0, a_Player);
|
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;
|
cBlockArea Area;
|
||||||
Area.Read(a_World, m_BlockPos - Vector3i(2, 0, 2), m_BlockPos + Vector3i(2, 1, 2));
|
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}
|
{ 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++)
|
for (size_t i = 0; i < ARRAYCOUNT(CheckCoords); i++)
|
||||||
{
|
{
|
||||||
if (
|
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:
|
// cSlotAreaEnderChest:
|
||||||
|
|
||||||
|
@ -397,14 +397,20 @@ public:
|
|||||||
virtual void OnPlayerAdded (cPlayer & a_Player) override;
|
virtual void OnPlayerAdded (cPlayer & a_Player) override;
|
||||||
virtual void OnPlayerRemoved(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 */
|
/* Get the number of bookshelves which are near the enchanting table */
|
||||||
int GetBookshelvesCount(cWorld & a_World);
|
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:
|
protected:
|
||||||
|
|
||||||
/** Handles a click in the item slot. */
|
/** Handles a click in the item slot. */
|
||||||
void UpdateResult(cPlayer & a_Player);
|
void UpdateResult(cPlayer & a_Player);
|
||||||
|
|
||||||
Vector3i m_BlockPos;
|
Vector3i m_BlockPos;
|
||||||
|
std::array<cItem, 3> m_EnchantedItemOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user