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:
parent
1e014a54dc
commit
57690b81a2
@ -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.",
|
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 =
|
DoWithNoteBlockAt =
|
||||||
{
|
{
|
||||||
Params =
|
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)",
|
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 =
|
TryGetHeight =
|
||||||
{
|
{
|
||||||
Params =
|
Params =
|
||||||
|
@ -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)
|
static int tolua_cWorld_ForEachLoadedChunk(lua_State * tolua_S)
|
||||||
{
|
{
|
||||||
// Exported manually, because tolua doesn't support converting functions to functor types.
|
// 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)
|
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
|
/* 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, "DoWithFlowerPotAt", DoWithXYZ<cWorld, cFlowerPotEntity, &cWorld::DoWithFlowerPotAt>);
|
||||||
tolua_function(tolua_S, "DoWithFurnaceAt", DoWithXYZ<cWorld, cFurnaceEntity, &cWorld::DoWithFurnaceAt>);
|
tolua_function(tolua_S, "DoWithFurnaceAt", DoWithXYZ<cWorld, cFurnaceEntity, &cWorld::DoWithFurnaceAt>);
|
||||||
tolua_function(tolua_S, "DoWithMobHeadAt", DoWithXYZ<cWorld, cMobHeadEntity, &cWorld::DoWithMobHeadAt>);
|
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, "DoWithNoteBlockAt", DoWithXYZ<cWorld, cNoteEntity, &cWorld::DoWithNoteBlockAt>);
|
||||||
tolua_function(tolua_S, "DoWithPlayer", DoWith< cWorld, cPlayer, &cWorld::DoWithPlayer>);
|
tolua_function(tolua_S, "DoWithPlayer", DoWith< cWorld, cPlayer, &cWorld::DoWithPlayer>);
|
||||||
tolua_function(tolua_S, "DoWithPlayerByUUID", tolua_cWorld_DoWithPlayerByUUID);
|
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, "QueueTask", tolua_cWorld_QueueTask);
|
||||||
tolua_function(tolua_S, "ScheduleTask", tolua_cWorld_ScheduleTask);
|
tolua_function(tolua_S, "ScheduleTask", tolua_cWorld_ScheduleTask);
|
||||||
tolua_function(tolua_S, "SetSignLines", tolua_cWorld_SetSignLines);
|
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_function(tolua_S, "TryGetHeight", tolua_cWorld_TryGetHeight);
|
||||||
tolua_endmodule(tolua_S);
|
tolua_endmodule(tolua_S);
|
||||||
tolua_endmodule(tolua_S);
|
tolua_endmodule(tolua_S);
|
||||||
|
@ -46,6 +46,6 @@ public:
|
|||||||
|
|
||||||
auto & Random = GetRandomProvider();
|
auto & Random = GetRandomProvider();
|
||||||
int Reward = 15 + Random.RandInt(14) + Random.RandInt(14);
|
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);
|
||||||
}
|
}
|
||||||
} ;
|
} ;
|
||||||
|
@ -121,7 +121,7 @@ public:
|
|||||||
|
|
||||||
if (Reward != 0)
|
if (Reward != 0)
|
||||||
{
|
{
|
||||||
a_WorldInterface.SpawnExperienceOrb(a_BlockX, a_BlockY, a_BlockZ, Reward);
|
a_WorldInterface.SpawnSplitExperienceOrbs(a_BlockX, a_BlockY, a_BlockZ, Reward);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} ;
|
} ;
|
||||||
|
@ -52,6 +52,10 @@ public:
|
|||||||
Returns the UniqueID of the spawned experience orb, or cEntity::INVALID_ID on failure. */
|
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;
|
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 */
|
/** 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;
|
virtual void SendBlockTo(int a_BlockX, int a_BlockY, int a_BlockZ, cPlayer & a_Player) = 0;
|
||||||
|
|
||||||
|
@ -12,7 +12,8 @@ cExpOrb::cExpOrb(double a_X, double a_Y, double a_Z, int a_Reward)
|
|||||||
{
|
{
|
||||||
SetMaxHealth(5);
|
SetMaxHealth(5);
|
||||||
SetHealth(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);
|
SetMaxHealth(5);
|
||||||
SetHealth(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)
|
void cExpOrb::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
||||||
{
|
{
|
||||||
DetectCacti();
|
DetectCacti();
|
||||||
|
m_TicksAlive++;
|
||||||
|
|
||||||
// Check player proximity no more than twice per second
|
// Find closest player within 6.5 meter (slightly increase detect range to have same effect in client)
|
||||||
if ((m_TicksAlive % 10) == 0)
|
bool FoundPlayer = m_World->DoWithNearestPlayer(GetPosition(), 6.5, [&](cPlayer & a_Player) -> bool
|
||||||
{
|
{
|
||||||
cPlayer * a_ClosestPlayer(m_World->FindClosestPlayer(Vector3f(GetPosition()), 5, false));
|
Vector3f a_PlayerPos(a_Player.GetPosition());
|
||||||
if ((a_ClosestPlayer != nullptr) && (!a_ClosestPlayer->IsGameModeSpectator()))
|
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_Player.DeltaExperience(m_Reward);
|
||||||
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);
|
|
||||||
|
|
||||||
m_World->BroadcastSoundEffect("entity.experience_orb.pickup", GetPosition(), 0.5f, (0.75f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64));
|
m_World->BroadcastSoundEffect("entity.experience_orb.pickup", GetPosition(), 0.5f, (0.75f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64));
|
||||||
|
|
||||||
Destroy(true);
|
Destroy(true);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
SetSpeedX((a_PlayerPos.x - GetPosition().x) * 2.0);
|
|
||||||
SetSpeedY((a_PlayerPos.y - GetPosition().y) * 2.0);
|
// Experience orb will "float" or glide toward the player up to a distance of 6 blocks.
|
||||||
SetSpeedZ((a_PlayerPos.z - GetPosition().z) * 2.0);
|
// 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);
|
HandlePhysics(a_Dt, a_Chunk);
|
||||||
|
BroadcastMovementUpdate();
|
||||||
|
|
||||||
m_Timer += a_Dt;
|
m_Timer += a_Dt;
|
||||||
if (m_Timer >= std::chrono::minutes(5))
|
if (m_Timer >= std::chrono::minutes(5))
|
||||||
@ -96,3 +117,30 @@ bool cExpOrb::DoTakeDamage(TakeDamageInfo & a_TDI)
|
|||||||
|
|
||||||
return super::DoTakeDamage(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,6 +44,9 @@ public:
|
|||||||
|
|
||||||
// tolua_end
|
// tolua_end
|
||||||
|
|
||||||
|
/** Split reward into small values according to regular Minecraft rules */
|
||||||
|
static std::vector<int> Split(int a_Reward);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int m_Reward;
|
int m_Reward;
|
||||||
|
|
||||||
|
@ -86,8 +86,14 @@ bool cMobSpawner::CanSpawnHere(cChunk * a_Chunk, int a_RelX, int a_RelY, int a_R
|
|||||||
auto & Random = GetRandomProvider();
|
auto & Random = GetRandomProvider();
|
||||||
BLOCKTYPE TargetBlock = a_Chunk->GetBlock(a_RelX, a_RelY, a_RelZ);
|
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 too close to any player, don't spawn anything
|
||||||
if (a_Closest_Player != nullptr) // Too close to a player, bail out
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -660,7 +660,7 @@ void cMonster::KilledBy(TakeDamageInfo & a_TDI)
|
|||||||
}
|
}
|
||||||
if ((a_TDI.Attacker != nullptr) && (!IsBaby()))
|
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);
|
m_DestroyTimer = std::chrono::milliseconds(0);
|
||||||
}
|
}
|
||||||
@ -712,13 +712,11 @@ void cMonster::OnRightClicked(cPlayer & a_Player)
|
|||||||
// monster sez: Do I see the player
|
// monster sez: Do I see the player
|
||||||
void cMonster::CheckEventSeePlayer(cChunk & a_Chunk)
|
void cMonster::CheckEventSeePlayer(cChunk & a_Chunk)
|
||||||
{
|
{
|
||||||
// TODO: Rewrite this to use cWorld's DoWithPlayers()
|
m_World->DoWithNearestPlayer(GetPosition(), static_cast<float>(m_SightDistance), [&](cPlayer & a_Player) -> bool
|
||||||
cPlayer * Closest = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance), false);
|
|
||||||
|
|
||||||
if (Closest != nullptr)
|
|
||||||
{
|
{
|
||||||
EventSeePlayer(Closest, a_Chunk);
|
EventSeePlayer(&a_Player, a_Chunk);
|
||||||
}
|
return true;
|
||||||
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,12 +47,11 @@ void cOcelot::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
{
|
{
|
||||||
if (m_CheckPlayerTickCount == 23)
|
if (m_CheckPlayerTickCount == 23)
|
||||||
{
|
{
|
||||||
cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), 10, true);
|
m_World->DoWithNearestPlayer(GetPosition(), 10, [&](cPlayer & a_Player) -> bool
|
||||||
if (a_Closest_Player != nullptr)
|
|
||||||
{
|
{
|
||||||
cItems Items;
|
cItems Items;
|
||||||
GetBreedingItems(Items);
|
GetBreedingItems(Items);
|
||||||
if (Items.ContainsType(a_Closest_Player->GetEquippedItem().m_ItemType))
|
if (Items.ContainsType(a_Player.GetEquippedItem().m_ItemType))
|
||||||
{
|
{
|
||||||
if (!IsBegging())
|
if (!IsBegging())
|
||||||
{
|
{
|
||||||
@ -60,7 +59,7 @@ void cOcelot::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
m_World->BroadcastEntityMetadata(*this);
|
m_World->BroadcastEntityMetadata(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
MoveToPosition(a_Closest_Player->GetPosition());
|
MoveToPosition(a_Player.GetPosition());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -70,8 +69,9 @@ void cOcelot::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
m_World->BroadcastEntityMetadata(*this);
|
m_World->BroadcastEntityMetadata(*this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, true);
|
||||||
m_CheckPlayerTickCount = 0;
|
m_CheckPlayerTickCount = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -136,16 +136,17 @@ void cPassiveMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
GetFollowedItems(FollowedItems);
|
GetFollowedItems(FollowedItems);
|
||||||
if (FollowedItems.Size() > 0)
|
if (FollowedItems.Size() > 0)
|
||||||
{
|
{
|
||||||
cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance));
|
m_World->DoWithNearestPlayer(GetPosition(), static_cast<float>(m_SightDistance), [&](cPlayer & a_Player) -> bool
|
||||||
if (a_Closest_Player != nullptr)
|
|
||||||
{
|
{
|
||||||
cItem EquippedItem = a_Closest_Player->GetEquippedItem();
|
cItem EquippedItem = a_Player.GetEquippedItem();
|
||||||
if (FollowedItems.ContainsType(EquippedItem))
|
if (FollowedItems.ContainsType(EquippedItem))
|
||||||
{
|
{
|
||||||
Vector3d PlayerPos = a_Closest_Player->GetPosition();
|
Vector3d PlayerPos = a_Player.GetPosition();
|
||||||
MoveToPosition(PlayerPos);
|
MoveToPosition(PlayerPos);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,10 +271,9 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
|
|
||||||
if (GetTarget() == nullptr)
|
if (GetTarget() == nullptr)
|
||||||
{
|
{
|
||||||
cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), static_cast<float>(m_SightDistance));
|
m_World->DoWithNearestPlayer(GetPosition(), static_cast<float>(m_SightDistance), [&](cPlayer & a_Player) -> bool
|
||||||
if (a_Closest_Player != nullptr)
|
|
||||||
{
|
{
|
||||||
switch (a_Closest_Player->GetEquippedItem().m_ItemType)
|
switch (a_Player.GetEquippedItem().m_ItemType)
|
||||||
{
|
{
|
||||||
case E_ITEM_BONE:
|
case E_ITEM_BONE:
|
||||||
case E_ITEM_RAW_BEEF:
|
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_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.
|
// Don't move to the player if the wolf is sitting.
|
||||||
if (!IsSitting())
|
if (!IsSitting())
|
||||||
{
|
{
|
||||||
MoveToPosition(a_Closest_Player->GetPosition());
|
MoveToPosition(a_Player.GetPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -310,7 +309,9 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1937,7 +1937,7 @@ void cSlotAreaFurnace::HandleSmeltItem(const cItem & a_Result, cPlayer & a_Playe
|
|||||||
int Reward = m_Furnace->GetAndResetReward();
|
int Reward = m_Furnace->GetAndResetReward();
|
||||||
if (Reward > 0)
|
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. */
|
/** TODO 2014-05-12 xdot: Figure out when to call this method. */
|
||||||
|
@ -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)
|
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;
|
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;
|
cPlayer * ClosestPlayer = nullptr;
|
||||||
|
|
||||||
cCSLock Lock(m_CSPlayers);
|
cCSLock Lock(m_CSPlayers);
|
||||||
@ -2829,6 +2872,12 @@ cPlayer * cWorld::FindClosestPlayer(Vector3d a_Pos, float a_SightLimit, bool a_C
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (a_IgnoreSpectator && (*itr)->IsGameModeSpectator())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Vector3f Pos = (*itr)->GetPosition();
|
Vector3f Pos = (*itr)->GetPosition();
|
||||||
double Distance = (Pos - a_Pos).Length();
|
double Distance = (Pos - a_Pos).Length();
|
||||||
|
|
||||||
@ -2850,7 +2899,15 @@ cPlayer * cWorld::FindClosestPlayer(Vector3d a_Pos, float a_SightLimit, bool a_C
|
|||||||
ClosestDistance = Distance;
|
ClosestDistance = Distance;
|
||||||
ClosestPlayer = *itr;
|
ClosestPlayer = *itr;
|
||||||
}
|
}
|
||||||
return ClosestPlayer;
|
|
||||||
|
if (ClosestPlayer)
|
||||||
|
{
|
||||||
|
return a_Callback(*ClosestPlayer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
12
src/World.h
12
src/World.h
@ -279,8 +279,8 @@ public:
|
|||||||
/** Finds a player from a partial or complete player name and calls the callback - case-insensitive */
|
/** 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 <<
|
bool FindAndDoWithPlayer(const AString & a_PlayerNameHint, cPlayerListCallback a_Callback); // >> EXPORTED IN MANUALBINDINGS <<
|
||||||
|
|
||||||
// TODO: This interface is dangerous - rewrite to DoWithClosestPlayer(pos, sight, action)
|
/** Calls the callback for nearest player for given position, Returns false if player not found, otherwise returns the same value as the callback */
|
||||||
cPlayer * FindClosestPlayer(Vector3d a_Pos, float a_SightLimit, bool a_CheckLineOfSight = true);
|
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 */
|
/** Finds the player over his uuid and calls the callback */
|
||||||
bool DoWithPlayerByUUID(const cUUID & a_PlayerUUID, cPlayerListCallback a_Callback); // >> EXPORTED IN MANUALBINDINGS <<
|
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. */
|
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;
|
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.
|
// 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)
|
UInt32 SpawnPrimedTNT(double a_X, double a_Y, double a_Z, int a_FuseTimeInSec = 80, double a_InitialVelocityCoeff = 1)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user