1
0
Fork 0

Experience orb (#4259)

* Replace cWorld::FindClosesPlayer with cWorld::DoWithClosestPlayer
* Implement experience reward splitting into the orb sizes used in vanilla
* Modified speed calculation in cExpOrb::Tick to make the orbs fly towards the player

Fixes #4216
This commit is contained in:
changyong guo 2018-08-02 22:59:10 +08:00 committed by peterbell10
parent 1e014a54dc
commit 57690b81a2
15 changed files with 316 additions and 55 deletions

View File

@ -968,6 +968,39 @@ function OnAllChunksAvailable()</pre> All return values from the callbacks are i
},
Notes = "If there is a mob head at the specified coords, calls the CallbackFunction with the {{cMobHeadEntity}} parameter representing the furnace. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cMobHeadEntity|MobHeadEntity}})</pre> The function returns false if there is no mob head, or if there is, it returns the bool value that the callback has returned.",
},
DoWithNearestPlayer =
{
Params =
{
{
Name = "Position",
Type = "Vector3d",
},
{
Name = "RangeLimit",
Type = "number",
},
{
Name = "CallbackFunction",
Type = "function",
},
{
Name = "CheckLineOfSight",
Type = "boolean",
},
{
Name = "IgnoreSpectator",
Type = "boolean",
},
},
Returns =
{
{
Type = "boolean",
},
},
Notes = "Calls the specified callback function with the {{cPlayer|player}} nearest to the specified position as its parameter, if they are still within the range limit. The CallbackFunction has the following signature: <pre class=\"prettyprint lang-lua\">function Callback({{cPlayer|Player}})</pre> The function returns false if the player was not found, or whatever bool value the callback returned if the player was found.",
},
DoWithNoteBlockAt =
{
Params =
@ -3473,6 +3506,28 @@ function OnAllChunksAvailable()</pre> All return values from the callbacks are i
Notes = "Spawns a {{cTNTEntity|primed TNT entity}} at the specified coords, with the given fuse ticks. The entity gets a random speed multiplied by the InitialVelocityCoeff, 1 being the default value. Returns the EntityID of the new spawned primed tnt, or {{cEntity#INVALID_ID|cEntity#INVALID_ID}} if no primed tnt was created. (DEPRECATED, use vector-parametered version)",
},
},
SpawnSplitExperienceOrbs =
{
Params =
{
{
Name = "Position",
Type = "Vector3d",
},
{
Name = "Reward",
Type = "number",
},
},
Returns =
{
{
Name = "EntityID",
Type = "table",
},
},
Notes = "Spawns experience orbs of the specified total value at the given location. The orbs' values are split according to regular Minecraft rules. Returns an array-table of UniqueID of all the orbs.",
},
TryGetHeight =
{
Params =

View File

@ -463,6 +463,52 @@ static int tolua_cWorld_DoWithPlayerByUUID(lua_State * tolua_S)
static int tolua_cWorld_DoWithNearestPlayer(lua_State * tolua_S)
{
// Check params:
cLuaState L(tolua_S);
if (
!L.CheckParamSelf("cWorld") ||
!L.CheckParamUserType(2, "Vector3<double>") ||
!L.CheckParamNumber(3) ||
!L.CheckParamFunction(4) ||
// Params 5 and 6 are optional bools, no check for those
!L.CheckParamEnd(7)
)
{
return 0;
}
// Get parameters:
cWorld * Self;
Vector3d * Position;
double RangeLimit;
cLuaState::cRef FnRef;
bool CheckLineOfSight = true, IgnoreSpectators = true; // Defaults for the optional params
L.GetStackValues(1, Self, Position, RangeLimit, FnRef, CheckLineOfSight, IgnoreSpectators);
if (!FnRef.IsValid())
{
return L.ApiParamError("Expected a valid callback function for parameter #3");
}
// Call the function:
bool res = Self->DoWithNearestPlayer(*Position, RangeLimit, [&](cPlayer & a_Player)
{
bool ret = false;
L.Call(FnRef, &a_Player, cLuaState::Return, ret);
return ret;
}, CheckLineOfSight, IgnoreSpectators);
// Push the result as the return value:
L.Push(res);
return 1;
}
static int tolua_cWorld_ForEachLoadedChunk(lua_State * tolua_S)
{
// Exported manually, because tolua doesn't support converting functions to functor types.
@ -830,6 +876,38 @@ static int tolua_cWorld_ScheduleTask(lua_State * tolua_S)
static int tolua_cWorld_SpawnSplitExperienceOrbs(lua_State* tolua_S)
{
cLuaState L(tolua_S);
if (
!L.CheckParamSelf("cWorld") ||
!L.CheckParamUserType(2, "Vector3<double>") ||
!L.CheckParamNumber(3) ||
!L.CheckParamEnd(4)
)
{
return 0;
}
cWorld * self = nullptr;
Vector3d * Position;
int Reward;
L.GetStackValues(1, self, Position, Reward);
if (self == nullptr)
{
tolua_error(tolua_S, "Invalid 'self' in function 'SpawnSplitExperienceOrbs'", nullptr);
return 0;
}
// Execute and push result:
L.Push(self->SpawnExperienceOrb(Position->x, Position->y, Position->z, Reward));
return 1;
}
static int tolua_cWorld_TryGetHeight(lua_State * tolua_S)
{
/* Exported manually, because tolua would require the out-only param a_Height to be used when calling
@ -897,6 +975,7 @@ void cManualBindings::BindWorld(lua_State * tolua_S)
tolua_function(tolua_S, "DoWithFlowerPotAt", DoWithXYZ<cWorld, cFlowerPotEntity, &cWorld::DoWithFlowerPotAt>);
tolua_function(tolua_S, "DoWithFurnaceAt", DoWithXYZ<cWorld, cFurnaceEntity, &cWorld::DoWithFurnaceAt>);
tolua_function(tolua_S, "DoWithMobHeadAt", DoWithXYZ<cWorld, cMobHeadEntity, &cWorld::DoWithMobHeadAt>);
tolua_function(tolua_S, "DoWithNearestPlayer", tolua_cWorld_DoWithNearestPlayer);
tolua_function(tolua_S, "DoWithNoteBlockAt", DoWithXYZ<cWorld, cNoteEntity, &cWorld::DoWithNoteBlockAt>);
tolua_function(tolua_S, "DoWithPlayer", DoWith< cWorld, cPlayer, &cWorld::DoWithPlayer>);
tolua_function(tolua_S, "DoWithPlayerByUUID", tolua_cWorld_DoWithPlayerByUUID);
@ -917,6 +996,7 @@ void cManualBindings::BindWorld(lua_State * tolua_S)
tolua_function(tolua_S, "QueueTask", tolua_cWorld_QueueTask);
tolua_function(tolua_S, "ScheduleTask", tolua_cWorld_ScheduleTask);
tolua_function(tolua_S, "SetSignLines", tolua_cWorld_SetSignLines);
tolua_function(tolua_S, "SpawnSplitExperienceOrbs", tolua_cWorld_SpawnSplitExperienceOrbs);
tolua_function(tolua_S, "TryGetHeight", tolua_cWorld_TryGetHeight);
tolua_endmodule(tolua_S);
tolua_endmodule(tolua_S);

View File

@ -46,6 +46,6 @@ public:
auto & Random = GetRandomProvider();
int Reward = 15 + Random.RandInt(14) + Random.RandInt(14);
a_WorldInterface.SpawnExperienceOrb(static_cast<double>(a_BlockX), static_cast<double>(a_BlockY + 1), static_cast<double>(a_BlockZ), Reward);
a_WorldInterface.SpawnSplitExperienceOrbs(static_cast<double>(a_BlockX), static_cast<double>(a_BlockY + 1), static_cast<double>(a_BlockZ), Reward);
}
} ;

View File

@ -121,7 +121,7 @@ public:
if (Reward != 0)
{
a_WorldInterface.SpawnExperienceOrb(a_BlockX, a_BlockY, a_BlockZ, Reward);
a_WorldInterface.SpawnSplitExperienceOrbs(a_BlockX, a_BlockY, a_BlockZ, Reward);
}
}
} ;

View File

@ -52,6 +52,10 @@ public:
Returns the UniqueID of the spawned experience orb, or cEntity::INVALID_ID on failure. */
virtual UInt32 SpawnExperienceOrb(double a_X, double a_Y, double a_Z, int a_Reward) = 0;
/** Spawns experience orbs of the specified total value at the given location. The orbs' values are split according to regular Minecraft rules.
Returns an vector of UniqueID of all the orbs. */
virtual std::vector<UInt32> SpawnSplitExperienceOrbs(double a_X, double a_Y, double a_Z, int a_Reward) = 0;
/** Sends the block on those coords to the player */
virtual void SendBlockTo(int a_BlockX, int a_BlockY, int a_BlockZ, cPlayer & a_Player) = 0;

View File

@ -12,7 +12,8 @@ cExpOrb::cExpOrb(double a_X, double a_Y, double a_Z, int a_Reward)
{
SetMaxHealth(5);
SetHealth(5);
SetGravity(0);
SetGravity(-16);
SetAirDrag(0.02f);
}
@ -26,7 +27,8 @@ cExpOrb::cExpOrb(const Vector3d & a_Pos, int a_Reward)
{
SetMaxHealth(5);
SetHealth(5);
SetGravity(0);
SetGravity(-16);
SetAirDrag(0.02f);
}
@ -47,33 +49,52 @@ void cExpOrb::SpawnOn(cClientHandle & a_Client)
void cExpOrb::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
DetectCacti();
m_TicksAlive++;
// Check player proximity no more than twice per second
if ((m_TicksAlive % 10) == 0)
// Find closest player within 6.5 meter (slightly increase detect range to have same effect in client)
bool FoundPlayer = m_World->DoWithNearestPlayer(GetPosition(), 6.5, [&](cPlayer & a_Player) -> bool
{
cPlayer * a_ClosestPlayer(m_World->FindClosestPlayer(Vector3f(GetPosition()), 5, false));
if ((a_ClosestPlayer != nullptr) && (!a_ClosestPlayer->IsGameModeSpectator()))
Vector3f a_PlayerPos(a_Player.GetPosition());
a_PlayerPos.y += 0.8f;
Vector3f a_Distance = a_PlayerPos - GetPosition();
double Distance = a_Distance.Length();
if (Distance < 0.7f)
{
Vector3f a_PlayerPos(a_ClosestPlayer->GetPosition());
a_PlayerPos.y++;
Vector3f a_Distance(a_PlayerPos - GetPosition());
double Distance(a_Distance.Length());
if (Distance < 0.5f)
{
LOGD("Player %s picked up an ExpOrb. His reward is %i", a_ClosestPlayer->GetName().c_str(), m_Reward);
a_ClosestPlayer->DeltaExperience(m_Reward);
a_Player.DeltaExperience(m_Reward);
m_World->BroadcastSoundEffect("entity.experience_orb.pickup", GetPosition(), 0.5f, (0.75f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64));
Destroy(true);
return;
}
SetSpeedX((a_PlayerPos.x - GetPosition().x) * 2.0);
SetSpeedY((a_PlayerPos.y - GetPosition().y) * 2.0);
SetSpeedZ((a_PlayerPos.z - GetPosition().z) * 2.0);
m_World->BroadcastSoundEffect("entity.experience_orb.pickup", GetPosition(), 0.5f, (0.75f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64));
Destroy(true);
return true;
}
// Experience orb will "float" or glide toward the player up to a distance of 6 blocks.
// speeding up as they get nearer to the player, Speed range 6 - 12 m per second, accelerate 60 m per second^2
Vector3d SpeedDelta(a_Distance);
SpeedDelta.Normalize();
SpeedDelta *= 3;
Vector3d CurrentSpeed = GetSpeed();
CurrentSpeed += SpeedDelta;
if (CurrentSpeed.Length() > 12)
{
CurrentSpeed.Normalize();
CurrentSpeed *= 12;
}
SetSpeed(CurrentSpeed);
m_Gravity = 0;
return true;
}, false, true); // Don't check line of sight, ignore spectator mode player
if (!FoundPlayer)
{
m_Gravity = -16;
}
HandlePhysics(a_Dt, a_Chunk);
BroadcastMovementUpdate();
m_Timer += a_Dt;
if (m_Timer >= std::chrono::minutes(5))
@ -96,3 +117,30 @@ bool cExpOrb::DoTakeDamage(TakeDamageInfo & a_TDI)
return super::DoTakeDamage(a_TDI);
}
std::vector<int> cExpOrb::Split(int a_Reward)
{
const static std::array<int, 11> BaseValue = {{1, 3, 7, 17, 37, 73, 149, 307, 617, 1237, 2477}};
std::vector<int> Rewards;
size_t Index = BaseValue.size() - 1; // Last one
while (a_Reward > 0)
{
while (a_Reward < BaseValue[Index])
{
Index--;
}
a_Reward -= BaseValue[Index];
Rewards.push_back(BaseValue[Index]);
}
return Rewards;
}

View File

@ -44,6 +44,9 @@ public:
// tolua_end
/** Split reward into small values according to regular Minecraft rules */
static std::vector<int> Split(int a_Reward);
protected:
int m_Reward;

View File

@ -86,8 +86,14 @@ bool cMobSpawner::CanSpawnHere(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_R
auto & Random = GetRandomProvider();
BLOCKTYPE TargetBlock = a_Chunk->GetBlock(a_RelX, a_RelY, a_RelZ);
cPlayer * a_Closest_Player = a_Chunk->GetWorld()->FindClosestPlayer(a_Chunk->PositionToWorldPosition(a_RelX, a_RelY, a_RelZ), 24);
if (a_Closest_Player != nullptr) // Too close to a player, bail out
// If too close to any player, don't spawn anything
auto WorldPos = a_Chunk->PositionToWorldPosition(a_RelX, a_RelY, a_RelZ);
static const double RangeLimit = 24;
if (a_Chunk->GetWorld()->DoWithNearestPlayer(WorldPos, RangeLimit, [](cPlayer & a_Player) -> bool
{
return true;
})
)
{
return false;
}

View File

@ -660,7 +660,7 @@ void cMonster::KilledBy(TakeDamageInfo & a_TDI)
}
if ((a_TDI.Attacker != nullptr) && (!IsBaby()))
{
m_World->SpawnExperienceOrb(GetPosX(), GetPosY(), GetPosZ(), Reward);
m_World->SpawnSplitExperienceOrbs(GetPosX(), GetPosY(), GetPosZ(), Reward);
}
m_DestroyTimer = std::chrono::milliseconds(0);
}
@ -712,13 +712,11 @@ void cMonster::OnRightClicked(cPlayer & a_Player)
// monster sez: Do I see the player
void cMonster::CheckEventSeePlayer(cChunk & a_Chunk)
{
// TODO: Rewrite this to use cWorld's DoWithPlayers()
cPlayer * Closest = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance), false);
if (Closest != nullptr)
m_World->DoWithNearestPlayer(GetPosition(), static_cast<float>(m_SightDistance), [&](cPlayer & a_Player) -> bool
{
EventSeePlayer(Closest, a_Chunk);
}
EventSeePlayer(&a_Player, a_Chunk);
return true;
}, false);
}

View File

@ -47,12 +47,11 @@ void cOcelot::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
{
if (m_CheckPlayerTickCount == 23)
{
cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), 10, true);
if (a_Closest_Player != nullptr)
m_World->DoWithNearestPlayer(GetPosition(), 10, [&](cPlayer & a_Player) -> bool
{
cItems Items;
GetBreedingItems(Items);
if (Items.ContainsType(a_Closest_Player->GetEquippedItem().m_ItemType))
if (Items.ContainsType(a_Player.GetEquippedItem().m_ItemType))
{
if (!IsBegging())
{
@ -60,7 +59,7 @@ void cOcelot::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
m_World->BroadcastEntityMetadata(*this);
}
MoveToPosition(a_Closest_Player->GetPosition());
MoveToPosition(a_Player.GetPosition());
}
else
{
@ -70,8 +69,9 @@ void cOcelot::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
m_World->BroadcastEntityMetadata(*this);
}
}
}
return true;
}, true);
m_CheckPlayerTickCount = 0;
}
else

View File

@ -136,16 +136,17 @@ void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
GetFollowedItems(FollowedItems);
if (FollowedItems.Size() > 0)
{
cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance));
if (a_Closest_Player != nullptr)
m_World->DoWithNearestPlayer(GetPosition(), static_cast<float>(m_SightDistance), [&](cPlayer & a_Player) -> bool
{
cItem EquippedItem = a_Closest_Player->GetEquippedItem();
cItem EquippedItem = a_Player.GetEquippedItem();
if (FollowedItems.ContainsType(EquippedItem))
{
Vector3d PlayerPos = a_Closest_Player->GetPosition();
Vector3d PlayerPos = a_Player.GetPosition();
MoveToPosition(PlayerPos);
}
}
return true;
});
}
}

View File

@ -271,10 +271,9 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
if (GetTarget() == nullptr)
{
cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance));
if (a_Closest_Player != nullptr)
m_World->DoWithNearestPlayer(GetPosition(), static_cast<float>(m_SightDistance), [&](cPlayer & a_Player) -> bool
{
switch (a_Closest_Player->GetEquippedItem().m_ItemType)
switch (a_Player.GetEquippedItem().m_ItemType)
{
case E_ITEM_BONE:
case E_ITEM_RAW_BEEF:
@ -291,12 +290,12 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
m_World->BroadcastEntityMetadata(*this);
}
m_FinalDestination = a_Closest_Player->GetPosition(); // So that we will look at a player holding food
m_FinalDestination = a_Player.GetPosition(); // So that we will look at a player holding food
// Don't move to the player if the wolf is sitting.
if (!IsSitting())
{
MoveToPosition(a_Closest_Player->GetPosition());
MoveToPosition(a_Player.GetPosition());
}
break;
@ -310,7 +309,9 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
}
}
}
}
return true;
});
}
else
{

View File

@ -1937,7 +1937,7 @@ void cSlotAreaFurnace::HandleSmeltItem(const cItem & a_Result, cPlayer & a_Playe
int Reward = m_Furnace->GetAndResetReward();
if (Reward > 0)
{
a_Player.GetWorld()->SpawnExperienceOrb(a_Player.GetPosX(), a_Player.GetPosY(), a_Player.GetPosZ(), Reward);
a_Player.GetWorld()->SpawnSplitExperienceOrbs(a_Player.GetPosX(), a_Player.GetPosY(), a_Player.GetPosZ(), Reward);
}
/** TODO 2014-05-12 xdot: Figure out when to call this method. */

View File

@ -2298,6 +2298,49 @@ UInt32 cWorld::SpawnExperienceOrb(double a_X, double a_Y, double a_Z, int a_Rewa
std::vector<UInt32> cWorld::SpawnSplitExperienceOrbs(double a_X, double a_Y, double a_Z, int a_Reward)
{
std::vector<UInt32> OrbsID;
if (a_Reward < 1)
{
LOGWARNING("%s: Attempting to create an experience orb with non-positive reward!", __FUNCTION__);
return OrbsID;
}
std::vector<int> Rewards = cExpOrb::Split(a_Reward);
// Check generate number to decide speed limit (distribute range)
float SpeedLimit = (Rewards.size() / 2) + 5;
if (SpeedLimit > 10)
{
SpeedLimit = 10;
}
auto & Random = GetRandomProvider();
for (auto Reward : Rewards)
{
auto ExpOrb = cpp14::make_unique<cExpOrb>(a_X, a_Y, a_Z, Reward);
auto ExpOrbPtr = ExpOrb.get();
double SpeedX = Random.RandReal(-SpeedLimit, SpeedLimit);
double SpeedY = Random.RandReal(0.5);
double SpeedZ = Random.RandReal(-SpeedLimit, SpeedLimit);
ExpOrbPtr->SetSpeed(SpeedX, SpeedY, SpeedZ);
UInt32 Id = ExpOrbPtr->GetUniqueID();
if (ExpOrbPtr->Initialize(std::move(ExpOrb), *this))
{
OrbsID.push_back(Id);
}
}
return OrbsID;
}
UInt32 cWorld::SpawnMinecart(double a_X, double a_Y, double a_Z, int a_MinecartType, const cItem & a_Content, int a_BlockHeight)
{
std::unique_ptr<cMinecart> Minecart;
@ -2817,9 +2860,9 @@ bool cWorld::DoWithPlayerByUUID(const cUUID & a_PlayerUUID, cPlayerListCallback
cPlayer * cWorld::FindClosestPlayer(Vector3d a_Pos, float a_SightLimit, bool a_CheckLineOfSight)
bool cWorld::DoWithNearestPlayer(Vector3d a_Pos, double a_RangeLimit, cPlayerListCallback a_Callback, bool a_CheckLineOfSight, bool a_IgnoreSpectator)
{
double ClosestDistance = a_SightLimit;
double ClosestDistance = a_RangeLimit;
cPlayer * ClosestPlayer = nullptr;
cCSLock Lock(m_CSPlayers);
@ -2829,6 +2872,12 @@ cPlayer * cWorld::FindClosestPlayer(Vector3d a_Pos, float a_SightLimit, bool a_C
{
continue;
}
if (a_IgnoreSpectator && (*itr)->IsGameModeSpectator())
{
continue;
}
Vector3f Pos = (*itr)->GetPosition();
double Distance = (Pos - a_Pos).Length();
@ -2850,7 +2899,15 @@ cPlayer * cWorld::FindClosestPlayer(Vector3d a_Pos, float a_SightLimit, bool a_C
ClosestDistance = Distance;
ClosestPlayer = *itr;
}
return ClosestPlayer;
if (ClosestPlayer)
{
return a_Callback(*ClosestPlayer);
}
else
{
return false;
}
}

View File

@ -279,8 +279,8 @@ public:
/** Finds a player from a partial or complete player name and calls the callback - case-insensitive */
bool FindAndDoWithPlayer(const AString & a_PlayerNameHint, cPlayerListCallback a_Callback); // >> EXPORTED IN MANUALBINDINGS <<
// TODO: This interface is dangerous - rewrite to DoWithClosestPlayer(pos, sight, action)
cPlayer * FindClosestPlayer(Vector3d a_Pos, float a_SightLimit, bool a_CheckLineOfSight = true);
/** Calls the callback for nearest player for given position, Returns false if player not found, otherwise returns the same value as the callback */
bool DoWithNearestPlayer(Vector3d a_Pos, double a_RangeLimit, cPlayerListCallback a_Callback, bool a_CheckLineOfSight = true, bool a_IgnoreSpectator = true);
/** Finds the player over his uuid and calls the callback */
bool DoWithPlayerByUUID(const cUUID & a_PlayerUUID, cPlayerListCallback a_Callback); // >> EXPORTED IN MANUALBINDINGS <<
@ -465,6 +465,14 @@ public:
Returns the UniqueID of the spawned experience orb, or cEntity::INVALID_ID on failure. */
virtual UInt32 SpawnExperienceOrb(double a_X, double a_Y, double a_Z, int a_Reward) override;
// tolua_end
/** Spawns experience orbs of the specified total value at the given location. The orbs' values are split according to regular Minecraft rules.
Returns an vector of UniqueID of all the orbs. */
virtual std::vector<UInt32> SpawnSplitExperienceOrbs(double a_X, double a_Y, double a_Z, int a_Reward) override; // Exported in ManualBindings_World.cpp
// tolua_begin
// DEPRECATED, use the vector-parametered version instead.
UInt32 SpawnPrimedTNT(double a_X, double a_Y, double a_Z, int a_FuseTimeInSec = 80, double a_InitialVelocityCoeff = 1)
{