From 57690b81a24a29d70cb6f4196a6e0f521a3cb61b Mon Sep 17 00:00:00 2001 From: changyong guo Date: Thu, 2 Aug 2018 22:59:10 +0800 Subject: 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 --- Server/Plugins/APIDump/Classes/World.lua | 55 +++++++++++++++++++ src/Bindings/ManualBindings_World.cpp | 80 +++++++++++++++++++++++++++ src/Blocks/BlockMobSpawner.h | 2 +- src/Blocks/BlockOre.h | 2 +- src/Blocks/WorldInterface.h | 4 ++ src/Entities/ExpOrb.cpp | 94 ++++++++++++++++++++++++-------- src/Entities/ExpOrb.h | 3 + src/MobSpawner.cpp | 10 +++- src/Mobs/Monster.cpp | 12 ++-- src/Mobs/Ocelot.cpp | 10 ++-- src/Mobs/PassiveMonster.cpp | 11 ++-- src/Mobs/Wolf.cpp | 13 +++-- src/UI/SlotArea.cpp | 2 +- src/World.cpp | 63 ++++++++++++++++++++- src/World.h | 12 +++- 15 files changed, 317 insertions(+), 56 deletions(-) diff --git a/Server/Plugins/APIDump/Classes/World.lua b/Server/Plugins/APIDump/Classes/World.lua index bb991d9e6..8c19a44ef 100644 --- a/Server/Plugins/APIDump/Classes/World.lua +++ b/Server/Plugins/APIDump/Classes/World.lua @@ -968,6 +968,39 @@ function OnAllChunksAvailable() 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:
function Callback({{cMobHeadEntity|MobHeadEntity}})
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:
function Callback({{cPlayer|Player}})
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() 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 = diff --git a/src/Bindings/ManualBindings_World.cpp b/src/Bindings/ManualBindings_World.cpp index 9b51ab926..c6716cf91 100644 --- a/src/Bindings/ManualBindings_World.cpp +++ b/src/Bindings/ManualBindings_World.cpp @@ -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") || + !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") || + !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); tolua_function(tolua_S, "DoWithFurnaceAt", DoWithXYZ); tolua_function(tolua_S, "DoWithMobHeadAt", DoWithXYZ); + tolua_function(tolua_S, "DoWithNearestPlayer", tolua_cWorld_DoWithNearestPlayer); tolua_function(tolua_S, "DoWithNoteBlockAt", DoWithXYZ); 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); diff --git a/src/Blocks/BlockMobSpawner.h b/src/Blocks/BlockMobSpawner.h index f36e2b6eb..7911d3398 100644 --- a/src/Blocks/BlockMobSpawner.h +++ b/src/Blocks/BlockMobSpawner.h @@ -46,6 +46,6 @@ public: auto & Random = GetRandomProvider(); int Reward = 15 + Random.RandInt(14) + Random.RandInt(14); - a_WorldInterface.SpawnExperienceOrb(static_cast(a_BlockX), static_cast(a_BlockY + 1), static_cast(a_BlockZ), Reward); + a_WorldInterface.SpawnSplitExperienceOrbs(static_cast(a_BlockX), static_cast(a_BlockY + 1), static_cast(a_BlockZ), Reward); } } ; diff --git a/src/Blocks/BlockOre.h b/src/Blocks/BlockOre.h index 0c72e39f9..0a52de3d6 100644 --- a/src/Blocks/BlockOre.h +++ b/src/Blocks/BlockOre.h @@ -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); } } } ; diff --git a/src/Blocks/WorldInterface.h b/src/Blocks/WorldInterface.h index 344eb9ca3..e49283402 100644 --- a/src/Blocks/WorldInterface.h +++ b/src/Blocks/WorldInterface.h @@ -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 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; diff --git a/src/Entities/ExpOrb.cpp b/src/Entities/ExpOrb.cpp index ecc2860b0..eda96bbc5 100644 --- a/src/Entities/ExpOrb.cpp +++ b/src/Entities/ExpOrb.cpp @@ -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) + { + a_Player.DeltaExperience(m_Reward); + + m_World->BroadcastSoundEffect("entity.experience_orb.pickup", GetPosition(), 0.5f, (0.75f + (static_cast((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) { - 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); - - m_World->BroadcastSoundEffect("entity.experience_orb.pickup", GetPosition(), 0.5f, (0.75f + (static_cast((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); + 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 cExpOrb::Split(int a_Reward) +{ + const static std::array BaseValue = {{1, 3, 7, 17, 37, 73, 149, 307, 617, 1237, 2477}}; + + std::vector 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; +} + + diff --git a/src/Entities/ExpOrb.h b/src/Entities/ExpOrb.h index 20ac6e304..c4519e963 100644 --- a/src/Entities/ExpOrb.h +++ b/src/Entities/ExpOrb.h @@ -44,6 +44,9 @@ public: // tolua_end + /** Split reward into small values according to regular Minecraft rules */ + static std::vector Split(int a_Reward); + protected: int m_Reward; diff --git a/src/MobSpawner.cpp b/src/MobSpawner.cpp index f04cfa178..0e41e72ec 100644 --- a/src/MobSpawner.cpp +++ b/src/MobSpawner.cpp @@ -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; } diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 3c202d693..5327da832 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -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(m_SightDistance), false); - - if (Closest != nullptr) + m_World->DoWithNearestPlayer(GetPosition(), static_cast(m_SightDistance), [&](cPlayer & a_Player) -> bool { - EventSeePlayer(Closest, a_Chunk); - } + EventSeePlayer(&a_Player, a_Chunk); + return true; + }, false); } diff --git a/src/Mobs/Ocelot.cpp b/src/Mobs/Ocelot.cpp index 02af45a7d..855a11627 100644 --- a/src/Mobs/Ocelot.cpp +++ b/src/Mobs/Ocelot.cpp @@ -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 diff --git a/src/Mobs/PassiveMonster.cpp b/src/Mobs/PassiveMonster.cpp index c9345662d..cd0f59153 100644 --- a/src/Mobs/PassiveMonster.cpp +++ b/src/Mobs/PassiveMonster.cpp @@ -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(m_SightDistance)); - if (a_Closest_Player != nullptr) + m_World->DoWithNearestPlayer(GetPosition(), static_cast(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; + }); } } diff --git a/src/Mobs/Wolf.cpp b/src/Mobs/Wolf.cpp index 401175bf0..74924ab11 100644 --- a/src/Mobs/Wolf.cpp +++ b/src/Mobs/Wolf.cpp @@ -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(m_SightDistance)); - if (a_Closest_Player != nullptr) + m_World->DoWithNearestPlayer(GetPosition(), static_cast(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 { diff --git a/src/UI/SlotArea.cpp b/src/UI/SlotArea.cpp index 19c75d196..0c69e33b0 100644 --- a/src/UI/SlotArea.cpp +++ b/src/UI/SlotArea.cpp @@ -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. */ diff --git a/src/World.cpp b/src/World.cpp index 0c4618606..c832f7386 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -2298,6 +2298,49 @@ UInt32 cWorld::SpawnExperienceOrb(double a_X, double a_Y, double a_Z, int a_Rewa +std::vector cWorld::SpawnSplitExperienceOrbs(double a_X, double a_Y, double a_Z, int a_Reward) +{ + std::vector OrbsID; + + if (a_Reward < 1) + { + LOGWARNING("%s: Attempting to create an experience orb with non-positive reward!", __FUNCTION__); + return OrbsID; + } + + std::vector 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(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 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; + } } diff --git a/src/World.h b/src/World.h index 1deae2b74..faccb01f0 100644 --- a/src/World.h +++ b/src/World.h @@ -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 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) { -- cgit v1.2.3