From cb640ffea4a51a10db2e379ada8dc0487378ba09 Mon Sep 17 00:00:00 2001 From: bibo38 Date: Wed, 12 Oct 2016 14:38:45 +0200 Subject: Spectators added (#2852) --- Server/Plugins/APIDump/APIDesc.lua | 28 ++++++++++++++ src/BlockInfo.cpp | 9 +++++ src/BlockInfo.h | 5 +++ src/ByteBuffer.cpp | 33 +++++++++++++++++ src/ByteBuffer.h | 1 + src/Chunk.cpp | 11 +++++- src/Chunk.h | 1 + src/ChunkMap.cpp | 9 +++++ src/ChunkMap.h | 3 ++ src/ClientHandle.cpp | 56 +++++++++++++++++++++++++++- src/ClientHandle.h | 2 + src/Entities/Entity.h | 2 +- src/Entities/ExpOrb.cpp | 2 +- src/Entities/Minecart.cpp | 10 ++++- src/Entities/Pawn.cpp | 6 ++- src/Entities/Pickup.cpp | 6 +++ src/Entities/Player.cpp | 69 +++++++++++++++++++++++++++++++++++ src/Entities/Player.h | 4 ++ src/Entities/ProjectileEntity.cpp | 10 ++++- src/Mobs/AggressiveMonster.cpp | 11 ++++-- src/Mobs/AggressiveMonster.h | 3 +- src/Mobs/Enderman.cpp | 17 +++++---- src/Mobs/Monster.cpp | 15 +++++--- src/Mobs/Monster.h | 2 +- src/Mobs/PassiveAggressiveMonster.cpp | 5 ++- src/Mobs/PassiveAggressiveMonster.h | 2 +- src/Mobs/Spider.cpp | 6 +-- src/Mobs/Spider.h | 2 +- src/Protocol/Protocol.h | 1 + src/Protocol/Protocol18x.cpp | 26 +++++++++++++ src/Protocol/Protocol18x.h | 2 + src/Protocol/Protocol19x.cpp | 27 +++++++++++++- src/Protocol/Protocol19x.h | 2 + src/Protocol/ProtocolRecognizer.cpp | 9 +++++ src/Protocol/ProtocolRecognizer.h | 1 + src/UI/SlotArea.cpp | 8 ++++ src/World.cpp | 22 ++++++++++- src/World.h | 4 ++ 38 files changed, 396 insertions(+), 36 deletions(-) diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index 15f621711..1ab38dd8d 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -2220,6 +2220,24 @@ return }, Notes = "Returns whether the specified block is transparent.", }, + IsUseableBySpectator = + { + IsStatic = true, + Params = + { + { + Name = "BlockType", + Type = "number", + }, + }, + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns whether a spectator can interact with the specified block.", + }, RequiresSpecialTool = { IsStatic = true, @@ -10949,6 +10967,16 @@ a_Player:OpenWindow(Window); }, Notes = "Returns if the player is able to fly.", }, + CanMobsTarget = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns if the player can be targeted by mobs.", + }, CloseWindow = { Params = diff --git a/src/BlockInfo.cpp b/src/BlockInfo.cpp index 2afb5d6f2..34ccc2378 100644 --- a/src/BlockInfo.cpp +++ b/src/BlockInfo.cpp @@ -492,6 +492,15 @@ void cBlockInfo::Initialize(cBlockInfoArray & a_Info) a_Info[E_BLOCK_WOODEN_PRESSURE_PLATE].m_IsSolid = false; + // Blocks, which a spectator is allowed to interact with + a_Info[E_BLOCK_BEACON ].m_UseableBySpectator = true; + a_Info[E_BLOCK_BREWING_STAND ].m_UseableBySpectator = true; + a_Info[E_BLOCK_CHEST ].m_UseableBySpectator = true; + a_Info[E_BLOCK_DISPENSER ].m_UseableBySpectator = true; + a_Info[E_BLOCK_DROPPER ].m_UseableBySpectator = true; + a_Info[E_BLOCK_HOPPER ].m_UseableBySpectator = true; + + // Blocks that fully occupy their voxel - used as a guide for torch placeable blocks, amongst other things: a_Info[E_BLOCK_BARRIER ].m_FullyOccupiesVoxel = true; a_Info[E_BLOCK_BEDROCK ].m_FullyOccupiesVoxel = true; diff --git a/src/BlockInfo.h b/src/BlockInfo.h index c1eb926d1..d42987794 100644 --- a/src/BlockInfo.h +++ b/src/BlockInfo.h @@ -55,6 +55,9 @@ public: /** Is this block solid (player cannot walk through)? */ bool m_IsSolid; + /** Can a spectator interact with this block */ + bool m_UseableBySpectator; + /** Does this block fully occupy its voxel - is it a 'full' block? */ bool m_FullyOccupiesVoxel; @@ -81,6 +84,7 @@ public: inline static bool IsPistonBreakable (BLOCKTYPE a_Type) { return Get(a_Type).m_PistonBreakable; } inline static bool IsSnowable (BLOCKTYPE a_Type) { return Get(a_Type).m_IsSnowable; } inline static bool IsSolid (BLOCKTYPE a_Type) { return Get(a_Type).m_IsSolid; } + inline static bool IsUseableBySpectator (BLOCKTYPE a_Type) { return Get(a_Type).m_UseableBySpectator; } inline static bool FullyOccupiesVoxel (BLOCKTYPE a_Type) { return Get(a_Type).m_FullyOccupiesVoxel; } inline static bool CanBeTerraformed (BLOCKTYPE a_Type) { return Get(a_Type).m_CanBeTerraformed; } inline static float GetBlockHeight (BLOCKTYPE a_Type) { return Get(a_Type).m_BlockHeight; } @@ -103,6 +107,7 @@ protected: , m_PistonBreakable(false) , m_IsSnowable(false) , m_IsSolid(true) + , m_UseableBySpectator(false) , m_FullyOccupiesVoxel(false) , m_CanBeTerraformed(false) , m_BlockHeight(1.0) diff --git a/src/ByteBuffer.cpp b/src/ByteBuffer.cpp index b5f862a73..6cfb66f9a 100644 --- a/src/ByteBuffer.cpp +++ b/src/ByteBuffer.cpp @@ -53,6 +53,16 @@ Unfortunately it is very slow, so it is disabled even for regular DEBUG builds. +static char ValueToHexDigit(UInt8 digit) +{ + ASSERT(digit < 16); + return "0123456789abcdef"[digit]; +} + + + + + #ifdef DEBUG_SINGLE_THREAD_ACCESS /** Simple RAII class that is used for checking that no two threads are using an object simultanously. @@ -517,6 +527,29 @@ bool cByteBuffer::ReadPosition64(int & a_BlockX, int & a_BlockY, int & a_BlockZ) +bool cByteBuffer::ReadUUID(AString & a_Value) +{ + CHECK_THREAD + + if (!ReadString(a_Value, 16)) + { + return false; + } + + a_Value.resize(32); + for (unsigned int i = 15; i < 16; i--) + { + a_Value[i * 2 + 1] = ValueToHexDigit(a_Value[i] & 0xf); + a_Value[i * 2] = ValueToHexDigit(static_cast(a_Value[i]) >> 4); + } + + return true; +} + + + + + bool cByteBuffer::WriteBEInt8(Int8 a_Value) { CHECK_THREAD diff --git a/src/ByteBuffer.h b/src/ByteBuffer.h index 128a907b2..761112721 100644 --- a/src/ByteBuffer.h +++ b/src/ByteBuffer.h @@ -68,6 +68,7 @@ public: bool ReadVarUTF8String (AString & a_Value); // string length as VarInt, then string as UTF-8 bool ReadLEInt (int & a_Value); bool ReadPosition64 (int & a_BlockX, int & a_BlockY, int & a_BlockZ); + bool ReadUUID (AString & a_Value); // UUID without dashes /** Reads VarInt, assigns it to anything that can be assigned from an UInt64 (unsigned short, char, Byte, double, ...) */ template bool ReadVarInt(T & a_Value) diff --git a/src/Chunk.cpp b/src/Chunk.cpp index 508fe355e..28a7c7272 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -2051,13 +2051,22 @@ bool cChunk::ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_ bool cChunk::DoWithEntityByID(UInt32 a_EntityID, cEntityCallback & a_Callback, bool & a_CallbackResult) +{ + return DoWithEntityByID(a_EntityID, std::bind(&cEntityCallback::Item, &a_Callback, std::placeholders::_1), a_CallbackResult); +} + + + + + +bool cChunk::DoWithEntityByID(UInt32 a_EntityID, cLambdaEntityCallback a_Callback, bool & a_CallbackResult) { // The entity list is locked by the parent chunkmap's CS for (cEntityList::iterator itr = m_Entities.begin(), end = m_Entities.end(); itr != end; ++itr) { if (((*itr)->GetUniqueID() == a_EntityID) && ((*itr)->IsTicking())) { - a_CallbackResult = a_Callback.Item(*itr); + a_CallbackResult = a_Callback(*itr); return true; } } // for itr - m_Entitites[] diff --git a/src/Chunk.h b/src/Chunk.h index 398d33a5f..aae3b98f0 100644 --- a/src/Chunk.h +++ b/src/Chunk.h @@ -274,6 +274,7 @@ public: /** Calls the callback if the entity with the specified ID is found, with the entity object as the callback param. Returns true if entity found. */ bool DoWithEntityByID(UInt32 a_EntityID, cEntityCallback & a_Callback, bool & a_CallbackResult); // Lua-accessible + bool DoWithEntityByID(UInt32 a_EntityID, cLambdaEntityCallback a_Callback, bool & a_CallbackResult); // Lambda version /** Calls the callback for each block entity; returns true if all block entities processed, false if the callback aborted by returning true */ bool ForEachBlockEntity(cBlockEntityCallback & a_Callback); // Lua-accessible diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index c8e485cdd..e608f6b30 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -1873,6 +1873,15 @@ void cChunkMap::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_ bool cChunkMap::DoWithEntityByID(UInt32 a_UniqueID, cEntityCallback & a_Callback) +{ + return DoWithEntityByID(a_UniqueID, std::bind(&cEntityCallback::Item, &a_Callback, std::placeholders::_1)); +} + + + + + +bool cChunkMap::DoWithEntityByID(UInt32 a_UniqueID, cLambdaEntityCallback a_Callback) { cCSLock Lock(m_CSChunks); bool res = false; diff --git a/src/ChunkMap.h b/src/ChunkMap.h index ff8f82f91..871881483 100644 --- a/src/ChunkMap.h +++ b/src/ChunkMap.h @@ -56,6 +56,8 @@ typedef cItemCallback cCommandBlockCallback; typedef cItemCallback cMobHeadCallback; typedef cItemCallback cChunkCallback; +typedef std::function cLambdaEntityCallback; + @@ -237,6 +239,7 @@ public: /** Calls the callback if the entity with the specified ID is found, with the entity object as the callback param. Returns true if entity found and callback returned false. */ bool DoWithEntityByID(UInt32 a_EntityID, cEntityCallback & a_Callback); // Lua-accessible + bool DoWithEntityByID(UInt32 a_EntityID, cLambdaEntityCallback a_Callback); // Lambda version /** Calls the callback for each block entity in the specified chunk. Returns true if all block entities processed, false if the callback aborted by returning true. */ diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 8df60ad40..469095b19 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -7,6 +7,7 @@ #include "Entities/Pickup.h" #include "Bindings/PluginManager.h" #include "Entities/Player.h" +#include "Entities/Minecart.h" #include "Inventory.h" #include "EffectID.h" #include "BlockEntities/BeaconEntity.h" @@ -1412,7 +1413,7 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta); cBlockHandler * BlockHandler = cBlockInfo::GetHandler(BlockType); - if (BlockHandler->IsUseable() && !m_Player->IsCrouched()) + if (BlockHandler->IsUseable() && !m_Player->IsCrouched() && (!m_Player->IsGameModeSpectator() || cBlockInfo::IsUseableBySpectator(BlockType))) { if (!PlgMgr->CallHookPlayerUsingBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta)) { @@ -1427,6 +1428,12 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, e } } + // Players, who spectate cannot use their items + if (m_Player->IsGameModeSpectator()) + { + return; + } + short EquippedDamage = Equipped.m_ItemDamage; cItemHandler * ItemHandler = cItemHandler::GetItemHandler(Equipped.m_ItemType); @@ -1557,6 +1564,19 @@ void cClientHandle::HandleSlotSelected(Int16 a_SlotNum) +void cClientHandle::HandleSpectate(const AString & a_PlayerUUID) +{ + m_Player->GetWorld()->DoWithPlayerByUUID(a_PlayerUUID, [=](cPlayer * a_ToSpectate) + { + m_Player->TeleportToEntity(*a_ToSpectate); + return true; + }); +} + + + + + void cClientHandle::HandleSteerVehicle(float a_Forward, float a_Sideways) { m_Player->SteerVehicle(a_Forward, a_Sideways); @@ -1617,6 +1637,17 @@ void cClientHandle::HandleUseEntity(UInt32 a_TargetEntityID, bool a_IsLeftClick) { // TODO: Let plugins interfere via a hook + // If the player is a spectator, let him spectate + if (m_Player->IsGameModeSpectator() && a_IsLeftClick) + { + m_Player->GetWorld()->DoWithEntityByID(a_TargetEntityID, [=](cEntity * a_Entity) + { + m_Player->AttachTo(a_Entity); + return true; + }); + return; + } + // If it is a right click, call the entity's OnRightClicked() handler: if (!a_IsLeftClick) { @@ -1625,7 +1656,19 @@ void cClientHandle::HandleUseEntity(UInt32 a_TargetEntityID, bool a_IsLeftClick) cPlayer & m_Player; virtual bool Item(cEntity * a_Entity) override { - if (cPluginManager::Get()->CallHookPlayerRightClickingEntity(m_Player, *a_Entity)) + if ( + cPluginManager::Get()->CallHookPlayerRightClickingEntity(m_Player, *a_Entity) || + ( + m_Player.IsGameModeSpectator() && // Spectators cannot interact with every entity + ( + !a_Entity->IsMinecart() || // They can only interact with minecarts + ( + (reinterpret_cast(a_Entity)->GetPayload() != cMinecart::mpChest) && // And only if the type matches a minecart with a chest or + (reinterpret_cast(a_Entity)->GetPayload() != cMinecart::mpHopper) // a minecart with a hopper + ) + ) + ) + ) { return false; } @@ -2178,6 +2221,15 @@ void cClientHandle::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlock +void cClientHandle::SendCameraSetTo(const cEntity & a_Entity) +{ + m_Protocol->SendCameraSetTo(a_Entity); +} + + + + + void cClientHandle::SendChat(const AString & a_Message, eMessageType a_ChatPrefix, const AString & a_AdditionalData) { cWorld * World = GetPlayer()->GetWorld(); diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 7d829653b..4a4c9553d 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -150,6 +150,7 @@ public: // tolua_export void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage); void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); // tolua_export void SendBlockChanges (int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes); + void SendCameraSetTo (const cEntity & a_Entity); void SendChat (const AString & a_Message, eMessageType a_ChatPrefix, const AString & a_AdditionalData = ""); void SendChat (const cCompositeChat & a_Message); void SendChatAboveActionBar (const AString & a_Message, eMessageType a_ChatPrefix, const AString & a_AdditionalData = ""); @@ -336,6 +337,7 @@ public: // tolua_export void HandleRespawn (void); void HandleRightClick (int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, const cItem & a_HeldItem); void HandleSlotSelected (Int16 a_SlotNum); + void HandleSpectate (const AString & a_PlayerUUID); void HandleSteerVehicle (float Forward, float Sideways); void HandleTabCompletion (const AString & a_Text); void HandleUpdateSign ( diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index c543fd9c1..4833f8b5c 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -429,7 +429,7 @@ public: cEntity * GetAttached(); /** Attaches to the specified entity; detaches from any previous one first */ - void AttachTo(cEntity * a_AttachTo); + virtual void AttachTo(cEntity * a_AttachTo); /** Detaches from the currently attached entity, if any */ virtual void Detach(void); diff --git a/src/Entities/ExpOrb.cpp b/src/Entities/ExpOrb.cpp index f51bbb300..290f9e665 100644 --- a/src/Entities/ExpOrb.cpp +++ b/src/Entities/ExpOrb.cpp @@ -45,7 +45,7 @@ void cExpOrb::SpawnOn(cClientHandle & a_Client) void cExpOrb::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) { cPlayer * a_ClosestPlayer(m_World->FindClosestPlayer(Vector3f(GetPosition()), 5)); - if (a_ClosestPlayer != nullptr) + if ((a_ClosestPlayer != nullptr) && (!a_ClosestPlayer->IsGameModeSpectator())) { Vector3f a_PlayerPos(a_ClosestPlayer->GetPosition()); a_PlayerPos.y++; diff --git a/src/Entities/Minecart.cpp b/src/Entities/Minecart.cpp index 43291bdc8..88b5959d5 100644 --- a/src/Entities/Minecart.cpp +++ b/src/Entities/Minecart.cpp @@ -39,7 +39,15 @@ public: { ASSERT(a_Entity != nullptr); - if (!a_Entity->IsPlayer() && !a_Entity->IsMob() && !a_Entity->IsMinecart() && !a_Entity->IsBoat()) + if ( + ( + !a_Entity->IsPlayer() || + reinterpret_cast(a_Entity)->IsGameModeSpectator() // Spectators doesn't collide with anything + ) && + !a_Entity->IsMob() && + !a_Entity->IsMinecart() && + !a_Entity->IsBoat() + ) { return false; } diff --git a/src/Entities/Pawn.cpp b/src/Entities/Pawn.cpp index dbcaba591..4b42dbb57 100644 --- a/src/Entities/Pawn.cpp +++ b/src/Entities/Pawn.cpp @@ -108,7 +108,11 @@ void cPawn::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) } } Callback(this); - m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), GetWidth(), GetHeight()), Callback); + // Spectators cannot push entities around + if ((!IsPlayer()) || (!reinterpret_cast(this)->IsGameModeSpectator())) + { + m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), GetWidth(), GetHeight()), Callback); + } super::Tick(a_Dt, a_Chunk); if (!IsTicking()) diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp index 12d535f84..b1892e4cc 100644 --- a/src/Entities/Pickup.cpp +++ b/src/Entities/Pickup.cpp @@ -208,6 +208,12 @@ bool cPickup::CollectedBy(cPlayer & a_Dest) return false; // Not old enough } + // If the player is a spectator, he cannot collect anything + if (a_Dest.IsGameModeSpectator()) + { + return false; + } + if (cRoot::Get()->GetPluginManager()->CallHookCollectingPickup(a_Dest, *this)) { // LOG("Pickup %d cannot be collected by \"%s\", because a plugin has said no.", m_UniqueID, a_Dest->GetName().c_str()); diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 5ab5e4567..db4b07553 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -250,6 +250,20 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_Stats.AddValue(statMinutesPlayed, 1); + // Handle the player detach, when the player is in spectator mode + if ( + (IsGameModeSpectator()) && + (m_AttachedTo != nullptr) && + ( + (m_AttachedTo->IsDestroyed()) || // Watching entity destruction + (m_AttachedTo->GetHealth() <= 0) || // Watching entity dead + (IsCrouched()) // Or the player wants to be detached + ) + ) + { + Detach(); + } + // Handle a frozen player TickFreezeCode(); if (m_IsFrozen) @@ -1233,6 +1247,16 @@ bool cPlayer::IsGameModeSpectator(void) const + +bool cPlayer::CanMobsTarget(void) const +{ + return IsGameModeSurvival() || IsGameModeAdventure(); +} + + + + + void cPlayer::SetTeam(cTeam * a_Team) { if (m_Team == a_Team) @@ -1344,6 +1368,12 @@ void cPlayer::SetGameMode(eGameMode a_GameMode) return; } + // Detach, if the player is switching from or to the spectator mode + if ((m_GameMode == gmSpectator) || (a_GameMode == gmSpectator)) + { + Detach(); + } + m_GameMode = a_GameMode; m_ClientHandle->SendGameMode(a_GameMode); @@ -1379,6 +1409,13 @@ void cPlayer::SetCapabilities() { SetVisible(false); SetCanFly(true); + + // Clear the current dragging item of the player + if (GetWindow() != nullptr) + { + m_DraggingItem.Empty(); + GetClientHandle()->SendInventorySlot(-1, -1, m_DraggingItem); + } } else { @@ -2476,8 +2513,40 @@ bool cPlayer::PlaceBlocks(const sSetBlockVector & a_Blocks) +void cPlayer::AttachTo(cEntity * a_AttachTo) +{ + // Different attach, if this is a spectator + if (IsGameModeSpectator()) + { + m_AttachedTo = a_AttachTo; + GetClientHandle()->SendCameraSetTo(*m_AttachedTo); + return; + } + + super::AttachTo(a_AttachTo); +} + + + + + void cPlayer::Detach() { + if (m_AttachedTo == nullptr) + { + // The player is not attached to anything. Bail out. + return; + } + + // Different detach, if this is a spectator + if (IsGameModeSpectator()) + { + GetClientHandle()->SendCameraSetTo(*this); + TeleportToEntity(*m_AttachedTo); + m_AttachedTo = nullptr; + return; + } + super::Detach(); int PosX = POSX_TOINT; int PosY = POSY_TOINT; diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 25796ee50..04cb5232b 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -193,6 +193,9 @@ public: /** Returns true if the player is in Spectator mode, either explicitly, or by inheriting from current world */ bool IsGameModeSpectator(void) const; + /** Returns true if the player can be targeted by Mobs */ + bool CanMobsTarget(void) const; + AString GetIP(void) const { return m_IP; } // tolua_export /** Returns the associated team, nullptr if none */ @@ -518,6 +521,7 @@ public: virtual bool IsSprinting(void) const override { return m_IsSprinting; } virtual bool IsRclking (void) const override { return IsEating() || IsChargingBow(); } + virtual void AttachTo(cEntity * a_AttachTo) override; virtual void Detach(void) override; /** Called by cClientHandle when the client is being destroyed. diff --git a/src/Entities/ProjectileEntity.cpp b/src/Entities/ProjectileEntity.cpp index fb7da85a1..3bded2b56 100644 --- a/src/Entities/ProjectileEntity.cpp +++ b/src/Entities/ProjectileEntity.cpp @@ -167,7 +167,15 @@ public: return false; } - if (!a_Entity->IsMob() && !a_Entity->IsMinecart() && !a_Entity->IsPlayer() && !a_Entity->IsBoat()) + if ( + !a_Entity->IsMob() && + !a_Entity->IsMinecart() && + ( + !a_Entity->IsPlayer() || + static_cast(a_Entity)->IsGameModeSpectator() + ) && + !a_Entity->IsBoat() + ) { // Not an entity that interacts with a projectile return false; diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp index 109ad274c..d8bdc4af5 100644 --- a/src/Mobs/AggressiveMonster.cpp +++ b/src/Mobs/AggressiveMonster.cpp @@ -36,13 +36,16 @@ void cAggressiveMonster::InStateChasing(std::chrono::milliseconds a_Dt, cChunk & -void cAggressiveMonster::EventSeePlayer(cEntity * a_Entity, cChunk & a_Chunk) + +void cAggressiveMonster::EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk) { - if (!static_cast(a_Entity)->IsGameModeCreative()) + if (!a_Player->CanMobsTarget()) { - super::EventSeePlayer(a_Entity, a_Chunk); - m_EMState = CHASING; + return; } + + super::EventSeePlayer(a_Player, a_Chunk); + m_EMState = CHASING; } diff --git a/src/Mobs/AggressiveMonster.h b/src/Mobs/AggressiveMonster.h index f2d6366e2..9ab8df06f 100644 --- a/src/Mobs/AggressiveMonster.h +++ b/src/Mobs/AggressiveMonster.h @@ -19,7 +19,8 @@ public: virtual void Tick (std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; virtual void InStateChasing(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; - virtual void EventSeePlayer(cEntity * a_Player, cChunk & a_Chunk) override; + + virtual void EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk) override; /** Try to perform attack returns true if attack was deemed successful (hit player, fired projectile, creeper exploded, etc.) even if it didn't actually do damage diff --git a/src/Mobs/Enderman.cpp b/src/Mobs/Enderman.cpp index 2ff547c3c..4e2e67f8a 100644 --- a/src/Mobs/Enderman.cpp +++ b/src/Mobs/Enderman.cpp @@ -23,8 +23,8 @@ public: virtual bool Item(cPlayer * a_Player) override { - // Don't check players who are in creative gamemode - if (a_Player->IsGameModeCreative()) + // Don't check players who cannot be targeted + if (!a_Player->CanMobsTarget()) { return false; } @@ -124,13 +124,16 @@ void cEnderman::CheckEventSeePlayer(cChunk & a_Chunk) return; } - if (!Callback.GetPlayer()->IsGameModeCreative()) + if (!Callback.GetPlayer()->CanMobsTarget()) { - cMonster::EventSeePlayer(Callback.GetPlayer(), a_Chunk); - m_EMState = CHASING; - m_bIsScreaming = true; - GetWorld()->BroadcastEntityMetadata(*this); + return; } + + // Target the player + cMonster::EventSeePlayer(Callback.GetPlayer(), a_Chunk); + m_EMState = CHASING; + m_bIsScreaming = true; + GetWorld()->BroadcastEntityMetadata(*this); } diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index acd8f0145..ece59828e 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -265,7 +265,7 @@ void cMonster::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) if (GetTarget()->IsPlayer()) { - if (static_cast(GetTarget())->IsGameModeCreative()) + if (!static_cast(GetTarget())->CanMobsTarget()) { SetTarget(nullptr); m_EMState = IDLE; @@ -471,7 +471,13 @@ bool cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) if ((a_TDI.Attacker != nullptr) && a_TDI.Attacker->IsPawn()) { - SetTarget(static_cast(a_TDI.Attacker)); + if ( + (!a_TDI.Attacker->IsPlayer()) || + (static_cast(a_TDI.Attacker)->CanMobsTarget()) + ) + { + SetTarget(static_cast(a_TDI.Attacker)); + } m_TicksSinceLastDamaged = 0; } return true; @@ -617,11 +623,10 @@ void cMonster::CheckEventLostPlayer(void) // What to do if player is seen // default to change state to chasing -void cMonster::EventSeePlayer(cEntity * a_SeenPlayer, cChunk & a_Chunk) +void cMonster::EventSeePlayer(cPlayer * a_SeenPlayer, cChunk & a_Chunk) { UNUSED(a_Chunk); - ASSERT(a_SeenPlayer->IsPlayer()); - SetTarget(static_cast(a_SeenPlayer)); + SetTarget(a_SeenPlayer); } diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index 03382e28e..1c3d9c37a 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -72,7 +72,7 @@ public: // tolua_end virtual void CheckEventSeePlayer(cChunk & a_Chunk); - virtual void EventSeePlayer(cEntity * a_Entity, cChunk & a_Chunk); + virtual void EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk); /** Reads the monster configuration for the specified monster name and assigns it to this object. */ void GetMonsterConfig(const AString & a_Name); diff --git a/src/Mobs/PassiveAggressiveMonster.cpp b/src/Mobs/PassiveAggressiveMonster.cpp index a1bb1138f..8715ba9c2 100644 --- a/src/Mobs/PassiveAggressiveMonster.cpp +++ b/src/Mobs/PassiveAggressiveMonster.cpp @@ -28,7 +28,7 @@ bool cPassiveAggressiveMonster::DoTakeDamage(TakeDamageInfo & a_TDI) if ((GetTarget() != nullptr) && (GetTarget()->IsPlayer())) { - if (!static_cast(GetTarget())->IsGameModeCreative()) + if (static_cast(GetTarget())->CanMobsTarget()) { m_EMState = CHASING; } @@ -39,7 +39,8 @@ bool cPassiveAggressiveMonster::DoTakeDamage(TakeDamageInfo & a_TDI) -void cPassiveAggressiveMonster::EventSeePlayer(cEntity *, cChunk & a_Chunk) + +void cPassiveAggressiveMonster::EventSeePlayer(cPlayer *, cChunk & a_Chunk) { // don't do anything, neutral mobs don't react to just seeing the player } diff --git a/src/Mobs/PassiveAggressiveMonster.h b/src/Mobs/PassiveAggressiveMonster.h index 00db75385..764e27779 100644 --- a/src/Mobs/PassiveAggressiveMonster.h +++ b/src/Mobs/PassiveAggressiveMonster.h @@ -16,7 +16,7 @@ public: cPassiveAggressiveMonster(const AString & a_ConfigName, eMonsterType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height); virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override; - virtual void EventSeePlayer(cEntity *, cChunk & a_Chunk) override; + virtual void EventSeePlayer(cPlayer *, cChunk & a_Chunk) override; } ; diff --git a/src/Mobs/Spider.cpp b/src/Mobs/Spider.cpp index a5f0d6a89..5ee3e3294 100644 --- a/src/Mobs/Spider.cpp +++ b/src/Mobs/Spider.cpp @@ -35,7 +35,7 @@ void cSpider::GetDrops(cItems & a_Drops, cEntity * a_Killer) -void cSpider::EventSeePlayer(cEntity * a_Entity, cChunk & a_Chunk) +void cSpider::EventSeePlayer(cPlayer * a_Player, cChunk & a_Chunk) { if (!GetWorld()->IsChunkLighted(GetChunkX(), GetChunkZ())) { @@ -48,9 +48,9 @@ void cSpider::EventSeePlayer(cEntity * a_Entity, cChunk & a_Chunk) return; } - if (!static_cast(a_Entity)->IsGameModeCreative() && (Chunk->GetSkyLightAltered(Rel.x, Rel.y, Rel.z) <= 9)) + if (a_Player->CanMobsTarget() && (Chunk->GetSkyLightAltered(Rel.x, Rel.y, Rel.z) <= 9)) { - super::EventSeePlayer(a_Entity, a_Chunk); + super::EventSeePlayer(a_Player, a_Chunk); } } diff --git a/src/Mobs/Spider.h b/src/Mobs/Spider.h index 85cae92fc..af2753012 100644 --- a/src/Mobs/Spider.h +++ b/src/Mobs/Spider.h @@ -18,7 +18,7 @@ public: CLASS_PROTODEF(cSpider) virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = nullptr) override; - virtual void EventSeePlayer(cEntity *, cChunk & a_Chunk) override; + virtual void EventSeePlayer(cPlayer *, cChunk & a_Chunk) override; virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override; } ; diff --git a/src/Protocol/Protocol.h b/src/Protocol/Protocol.h index 1da2a6fd7..3874307de 100644 --- a/src/Protocol/Protocol.h +++ b/src/Protocol/Protocol.h @@ -69,6 +69,7 @@ public: virtual void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) = 0; virtual void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) = 0; virtual void SendBlockChanges (int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) = 0; + virtual void SendCameraSetTo (const cEntity & a_Entity) = 0; virtual void SendChat (const AString & a_Message, eChatType a_Type) = 0; virtual void SendChat (const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) = 0; virtual void SendChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) = 0; diff --git a/src/Protocol/Protocol18x.cpp b/src/Protocol/Protocol18x.cpp index c1018324f..d75ab3a5c 100644 --- a/src/Protocol/Protocol18x.cpp +++ b/src/Protocol/Protocol18x.cpp @@ -246,6 +246,16 @@ void cProtocol180::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockV +void cProtocol180::SendCameraSetTo(const cEntity & a_Entity) +{ + cPacketizer Pkt(*this, 0x43); // Camera Packet (Attach the camera of a player at another entity in spectator mode) + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); +} + + + + + void cProtocol180::SendChat(const AString & a_Message, eChatType a_Type) { ASSERT(m_State == 3); // In game mode? @@ -2038,6 +2048,7 @@ bool cProtocol180::HandlePacket(cByteBuffer & a_ByteBuffer, UInt32 a_PacketType) case 0x15: HandlePacketClientSettings (a_ByteBuffer); return true; case 0x16: HandlePacketClientStatus (a_ByteBuffer); return true; case 0x17: HandlePacketPluginMessage (a_ByteBuffer); return true; + case 0x18: HandlePacketSpectate (a_ByteBuffer); return true; } break; } @@ -2495,6 +2506,21 @@ void cProtocol180::HandlePacketSlotSelect(cByteBuffer & a_ByteBuffer) +void cProtocol180::HandlePacketSpectate(cByteBuffer &a_ByteBuffer) +{ + AString playerUUID; + if (!a_ByteBuffer.ReadUUID(playerUUID)) + { + return; + } + + m_Client->HandleSpectate(playerUUID); +} + + + + + void cProtocol180::HandlePacketSteerVehicle(cByteBuffer & a_ByteBuffer) { HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Sideways); diff --git a/src/Protocol/Protocol18x.h b/src/Protocol/Protocol18x.h index b8f9675ba..6fc2647ed 100644 --- a/src/Protocol/Protocol18x.h +++ b/src/Protocol/Protocol18x.h @@ -65,6 +65,7 @@ public: virtual void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) override; virtual void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; virtual void SendBlockChanges (int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) override; + virtual void SendCameraSetTo (const cEntity & a_Entity) override; virtual void SendChat (const AString & a_Message, eChatType a_Type) override; virtual void SendChat (const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) override; virtual void SendChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) override; @@ -210,6 +211,7 @@ protected: void HandlePacketPlayerPosLook (cByteBuffer & a_ByteBuffer); void HandlePacketPluginMessage (cByteBuffer & a_ByteBuffer); void HandlePacketSlotSelect (cByteBuffer & a_ByteBuffer); + void HandlePacketSpectate (cByteBuffer & a_ByteBuffer); void HandlePacketSteerVehicle (cByteBuffer & a_ByteBuffer); void HandlePacketTabComplete (cByteBuffer & a_ByteBuffer); void HandlePacketUpdateSign (cByteBuffer & a_ByteBuffer); diff --git a/src/Protocol/Protocol19x.cpp b/src/Protocol/Protocol19x.cpp index 6791da8cd..6e26b2012 100644 --- a/src/Protocol/Protocol19x.cpp +++ b/src/Protocol/Protocol19x.cpp @@ -255,6 +255,16 @@ void cProtocol190::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSetBlockV +void cProtocol190::SendCameraSetTo(const cEntity & a_Entity) +{ + cPacketizer Pkt(*this, 0x36); // Camera Packet (Attach the camera of a player at another entity in spectator mode) + Pkt.WriteVarInt32(a_Entity.GetUniqueID()); +} + + + + + void cProtocol190::SendChat(const AString & a_Message, eChatType a_Type) { ASSERT(m_State == 3); // In game mode? @@ -2058,7 +2068,7 @@ bool cProtocol190::HandlePacket(cByteBuffer & a_ByteBuffer, UInt32 a_PacketType) case 0x18: HandlePacketCreativeInventoryAction(a_ByteBuffer); return true; case 0x19: HandlePacketUpdateSign (a_ByteBuffer); return true; case 0x1a: HandlePacketAnimation (a_ByteBuffer); return true; - case 0x1b: break; // Spectate? + case 0x1b: HandlePacketSpectate (a_ByteBuffer); return true; case 0x1c: HandlePacketBlockPlace (a_ByteBuffer); return true; case 0x1d: HandlePacketUseItem (a_ByteBuffer); return true; } @@ -2551,6 +2561,21 @@ void cProtocol190::HandlePacketSlotSelect(cByteBuffer & a_ByteBuffer) +void cProtocol190::HandlePacketSpectate(cByteBuffer & a_ByteBuffer) +{ + AString playerUUID; + if (!a_ByteBuffer.ReadUUID(playerUUID)) + { + return; + } + + m_Client->HandleSpectate(playerUUID); +} + + + + + void cProtocol190::HandlePacketSteerVehicle(cByteBuffer & a_ByteBuffer) { HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, Sideways); diff --git a/src/Protocol/Protocol19x.h b/src/Protocol/Protocol19x.h index 79180e3a7..d46da2a0f 100644 --- a/src/Protocol/Protocol19x.h +++ b/src/Protocol/Protocol19x.h @@ -71,6 +71,7 @@ public: virtual void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) override; virtual void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; virtual void SendBlockChanges (int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) override; + virtual void SendCameraSetTo (const cEntity & a_Entity) override; virtual void SendChat (const AString & a_Message, eChatType a_Type) override; virtual void SendChat (const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) override; virtual void SendChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) override; @@ -219,6 +220,7 @@ protected: void HandlePacketPluginMessage (cByteBuffer & a_ByteBuffer); void HandlePacketSlotSelect (cByteBuffer & a_ByteBuffer); void HandlePacketSteerVehicle (cByteBuffer & a_ByteBuffer); + void HandlePacketSpectate (cByteBuffer & a_ByteBuffer); void HandlePacketTabComplete (cByteBuffer & a_ByteBuffer); void HandlePacketUpdateSign (cByteBuffer & a_ByteBuffer); void HandlePacketUseEntity (cByteBuffer & a_ByteBuffer); diff --git a/src/Protocol/ProtocolRecognizer.cpp b/src/Protocol/ProtocolRecognizer.cpp index ca0d05c51..be97279a9 100644 --- a/src/Protocol/ProtocolRecognizer.cpp +++ b/src/Protocol/ProtocolRecognizer.cpp @@ -170,6 +170,15 @@ void cProtocolRecognizer::SendBlockChanges(int a_ChunkX, int a_ChunkZ, const sSe +void cProtocolRecognizer::SendCameraSetTo(const cEntity & a_Entity) +{ + ASSERT(m_Protocol != nullptr); + m_Protocol->SendCameraSetTo(a_Entity); +} + + + + void cProtocolRecognizer::SendChat(const AString & a_Message, eChatType a_Type) { diff --git a/src/Protocol/ProtocolRecognizer.h b/src/Protocol/ProtocolRecognizer.h index 6390b6289..24e3e214e 100644 --- a/src/Protocol/ProtocolRecognizer.h +++ b/src/Protocol/ProtocolRecognizer.h @@ -56,6 +56,7 @@ public: virtual void SendBlockBreakAnim (UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage) override; virtual void SendBlockChange (int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) override; virtual void SendBlockChanges (int a_ChunkX, int a_ChunkZ, const sSetBlockVector & a_Changes) override; + virtual void SendCameraSetTo (const cEntity & a_Entity) override; virtual void SendChat (const AString & a_Message, eChatType a_Type) override; virtual void SendChat (const cCompositeChat & a_Message, eChatType a_Type, bool a_ShouldUseChatPrefixes) override; virtual void SendChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer) override; diff --git a/src/UI/SlotArea.cpp b/src/UI/SlotArea.cpp index 2cef9b06d..8878bb900 100644 --- a/src/UI/SlotArea.cpp +++ b/src/UI/SlotArea.cpp @@ -58,6 +58,14 @@ void cSlotArea::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAction a_ClickA return; } + if (a_Player.IsGameModeSpectator()) + { + // Block the action of the player and make sure, the inventory doesn't get out of sync + a_Player.GetClientHandle()->SendInventorySlot(-1, -1, cItem()); // Reset the dragged item + SetSlot(a_SlotNum, a_Player, *GetSlot(a_SlotNum, a_Player)); // Update the current slot + return; + } + switch (a_ClickAction) { case caShiftLeftClick: diff --git a/src/World.cpp b/src/World.cpp index d47d0832a..cee1f8643 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -3132,6 +3132,15 @@ bool cWorld::FindAndDoWithPlayer(const AString & a_PlayerNameHint, cPlayerListCa bool cWorld::DoWithPlayerByUUID(const AString & a_PlayerUUID, cPlayerListCallback & a_Callback) +{ + return DoWithPlayerByUUID(a_PlayerUUID, std::bind(&cPlayerListCallback::Item, &a_Callback, std::placeholders::_1)); +} + + + + + +bool cWorld::DoWithPlayerByUUID(const AString & a_PlayerUUID, cLambdaPlayerCallback a_Callback) { cCSLock Lock(m_CSPlayers); for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr) @@ -3142,7 +3151,7 @@ bool cWorld::DoWithPlayerByUUID(const AString & a_PlayerUUID, cPlayerListCallbac } if ((*itr)->GetUUID() == a_PlayerUUID) { - return a_Callback.Item(*itr); + return a_Callback(*itr); } } return false; @@ -3240,6 +3249,15 @@ bool cWorld::ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_ bool cWorld::DoWithEntityByID(UInt32 a_UniqueID, cEntityCallback & a_Callback) +{ + return DoWithEntityByID(a_UniqueID, std::bind(&cEntityCallback::Item, &a_Callback, std::placeholders::_1)); +} + + + + + +bool cWorld::DoWithEntityByID(UInt32 a_UniqueID, cLambdaEntityCallback a_Callback) { // First check the entities-to-add: { @@ -3248,7 +3266,7 @@ bool cWorld::DoWithEntityByID(UInt32 a_UniqueID, cEntityCallback & a_Callback) { if (ent->GetUniqueID() == a_UniqueID) { - a_Callback.Item(ent); + a_Callback(ent); return true; } } // for ent - m_EntitiesToAdd[] diff --git a/src/World.h b/src/World.h index de34af1d2..0bcd1f823 100644 --- a/src/World.h +++ b/src/World.h @@ -78,6 +78,8 @@ typedef cItemCallback cCommandBlockCallback; typedef cItemCallback cMobHeadCallback; typedef cItemCallback cFlowerPotCallback; +typedef std::function cLambdaPlayerCallback; +typedef std::function cLambdaEntityCallback; @@ -288,6 +290,7 @@ public: /** Finds the player over his uuid and calls the callback */ bool DoWithPlayerByUUID(const AString & a_PlayerUUID, cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS << + bool DoWithPlayerByUUID(const AString & a_PlayerUUID, cLambdaPlayerCallback a_Callback); // Lambda version void SendPlayerList(cPlayer * a_DestPlayer); // Sends playerlist to the player @@ -313,6 +316,7 @@ public: /** Calls the callback if the entity with the specified ID is found, with the entity object as the callback param. Returns true if entity found and callback returned false. */ bool DoWithEntityByID(UInt32 a_UniqueID, cEntityCallback & a_Callback); // Exported in ManualBindings.cpp + bool DoWithEntityByID(UInt32 a_UniqueID, cLambdaEntityCallback a_Callback); // Lambda version /** Compares clients of two chunks, calls the callback accordingly */ void CompareChunkClients(int a_ChunkX1, int a_ChunkZ1, int a_ChunkX2, int a_ChunkZ2, cClientDiffCallback & a_Callback); -- cgit v1.2.3