From 258318ab98771f03d0109d7dfba81daaed26a2a0 Mon Sep 17 00:00:00 2001 From: DrButcher Date: Sun, 3 May 2020 22:05:32 +0200 Subject: [PATCH] Buttons can now be triggered by arrows. (#4670) * Buttons can now be triggered by arrows. --- Server/Plugins/APIDump/APIDesc.lua | 10 ++ src/Blocks/BlockButton.h | 147 +++++++++++++++--- src/Entities/ArrowEntity.cpp | 5 + src/Entities/Entity.h | 1 + .../PressurePlateHandler.h | 6 +- 5 files changed, 141 insertions(+), 28 deletions(-) diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index 5df216f49..ab31ad6b2 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -3664,6 +3664,16 @@ local Hash = cCryptoHash.sha1HexString("DataToHash") }, Notes = "Returns true if the entity class is a descendant of the specified class name, or the specified class itself", }, + IsArrow = + { + Returns = + { + { + Type = "boolean", + }, + }, + Notes = "Returns true if the entity is an arrow.", + }, IsBoat = { Returns = diff --git a/src/Blocks/BlockButton.h b/src/Blocks/BlockButton.h index 945c39b03..3a31b774e 100644 --- a/src/Blocks/BlockButton.h +++ b/src/Blocks/BlockButton.h @@ -4,6 +4,7 @@ #include "../BlockInfo.h" #include "../Chunk.h" #include "Mixins.h" +#include "ChunkInterface.h" @@ -36,7 +37,7 @@ public: NIBBLETYPE Meta = a_ChunkInterface.GetBlockMeta(a_BlockPos); // If button is already on, do nothing: - if (Meta & 0x08) + if (IsButtonOn(Meta)) { return false; } @@ -44,23 +45,14 @@ public: // Set the ON bit to on Meta |= 0x08; + const auto SoundToPlay = (m_BlockType == E_BLOCK_STONE_BUTTON) ? "block.stone_button.click_on" : "block.wood_button.click_on"; + a_ChunkInterface.SetBlockMeta(a_BlockPos, Meta, false); a_WorldInterface.WakeUpSimulators(a_BlockPos); - a_WorldInterface.GetBroadcastManager().BroadcastSoundEffect("block.stone_button.click_on", a_BlockPos, 0.5f, 0.6f); + a_WorldInterface.GetBroadcastManager().BroadcastSoundEffect(SoundToPlay, a_BlockPos, 0.5f, 0.6f, a_Player.GetClientHandle()); // Queue a button reset (unpress) - auto TickDelay = (m_BlockType == E_BLOCK_STONE_BUTTON) ? 20 : 30; - a_Player.GetWorld()->ScheduleTask(TickDelay, [a_BlockPos, this](cWorld & a_World) - { - if (a_World.GetBlock(a_BlockPos) == m_BlockType) - { - // Block hasn't change in the meantime; set its meta - a_World.SetBlockMeta(a_BlockPos, a_World.GetBlockMeta(a_BlockPos) & 0x07, false); - a_World.WakeUpSimulators(a_BlockPos); - a_World.BroadcastSoundEffect("block.stone_button.click_off", a_BlockPos, 0.5f, 0.5f); - } - } - ); + QueueButtonRelease(*a_Player.GetWorld(), a_BlockPos, m_BlockType); return true; } @@ -167,17 +159,122 @@ public: return 0; } - - - - - /** Extracts the ON bit from metadata. */ - static bool IsButtonOn(NIBBLETYPE a_BlockMeta) + /** Extracts the ON bit from metadata and returns if true if it is set */ + static bool IsButtonOn(NIBBLETYPE a_Meta) { - return ((a_BlockMeta & 0x8) == 0x8); + return (a_Meta & 0x08) == 0x08; + } + + /** Event handler for an arrow striking a block. + Performs appropriate handling if the arrow intersected a wooden button. */ + static void OnArrowHit(cWorld & a_World, const Vector3i a_Position, const eBlockFace a_HitFace) + { + BLOCKTYPE Type; + NIBBLETYPE Meta; + const auto Pos = AddFaceDirection(a_Position, a_HitFace); + + if ( + !a_World.GetBlockTypeMeta(Pos, Type, Meta) || + IsButtonOn(Meta) || + !IsButtonPressedByArrow(a_World, Pos, Type, Meta) + ) + { + // Bail if we're not specifically a wooden button, or it's already on + // or if the arrow didn't intersect. It is very important that nothing is + // done if the button is depressed, since the release task will already be queued + return; + } + + a_World.SetBlockMeta(Pos, Meta | 0x08, false); + a_World.WakeUpSimulators(Pos); + + // sound name is ok to be wood, because only wood gets triggered by arrow + a_World.GetBroadcastManager().BroadcastSoundEffect("block.wood_button.click_on", Pos, 0.5f, 0.6f); + + // Queue a button reset + QueueButtonRelease(a_World, Pos, Type); + } + +private: + + /** Schedules a recurring event at appropriate intervals to release a button at a given position. + The given block type is checked when the task is executed to ensure the position still contains a button. */ + static void QueueButtonRelease(cWorld & a_ButtonWorld, const Vector3i a_Position, const BLOCKTYPE a_BlockType) + { + const auto TickDelay = (a_BlockType == E_BLOCK_STONE_BUTTON) ? 20 : 30; + a_ButtonWorld.ScheduleTask( + TickDelay, + [a_Position, a_BlockType](cWorld & a_World) + { + BLOCKTYPE Type; + NIBBLETYPE Meta; + + if ( + !a_World.GetBlockTypeMeta(a_Position, Type, Meta) || + (Type != a_BlockType) || !IsButtonOn(Meta) + ) + { + // Total failure or block changed, bail + return; + } + + if (IsButtonPressedByArrow(a_World, a_Position, Type, Meta)) + { + // Try again in a little while + QueueButtonRelease(a_World, a_Position, a_BlockType); + return; + } + + // Block hasn't change in the meantime; release it + const auto SoundToPlayOnRelease = (Type == E_BLOCK_STONE_BUTTON) ? "block.stone_button.click_off" : "block.wood_button.click_off"; + + a_World.SetBlockMeta(a_Position, Meta & 0x07, false); + a_World.WakeUpSimulators(a_Position); + a_World.BroadcastSoundEffect(SoundToPlayOnRelease, a_Position, 0.5f, 0.5f); + } + ); + } + + /** Returns true if an arrow was found in the wooden button */ + static bool IsButtonPressedByArrow(cWorld & a_World, const Vector3i a_ButtonPosition, const BLOCKTYPE a_BlockType, const NIBBLETYPE a_Meta) + { + if (a_BlockType != E_BLOCK_WOODEN_BUTTON) + { + return false; + } + + const auto FaceOffset = GetButtonOffsetOnBlock(a_Meta); + const bool FoundArrow = !a_World.ForEachEntityInBox( + cBoundingBox(FaceOffset + a_ButtonPosition, 0.2, 0.2), + [](cEntity & a_Entity) + { + return a_Entity.IsArrow(); + } + ); + + return FoundArrow; + } + + /** Returns an offset to the integer world coordinates of a button. + Applying this offset yields the centre of the button's bounding box, + in terms of the position within the block the button with given meta occupies. + + TODO: this is only approximate, return the exact bbox instead. */ + static Vector3d GetButtonOffsetOnBlock(NIBBLETYPE a_Meta) + { + switch (BlockMetaDataToBlockFace(a_Meta)) + { + case BLOCK_FACE_YM: return { 0.5, 1, 0.5 }; + case BLOCK_FACE_XP: return { 0, 0.5, 0.5 }; + case BLOCK_FACE_XM: return { 1, 0.5, 0.5 }; + case BLOCK_FACE_ZP: return { 0.5, 0.5, 0 }; + case BLOCK_FACE_ZM: return { 0.5, 0.5, 1 }; + case BLOCK_FACE_YP: return { 0.5, 0, 0.5 }; + case BLOCK_FACE_NONE: + { + ASSERT(!"Unhandled block face!"); + return { 0, 0, 0 }; + } + } } } ; - - - - diff --git a/src/Entities/ArrowEntity.cpp b/src/Entities/ArrowEntity.cpp index 17c915742..ebb8524f1 100644 --- a/src/Entities/ArrowEntity.cpp +++ b/src/Entities/ArrowEntity.cpp @@ -3,6 +3,7 @@ #include "Player.h" #include "ArrowEntity.h" #include "../Chunk.h" +#include "../Blocks/BlockButton.h" @@ -84,6 +85,10 @@ void cArrowEntity::OnHitSolidBlock(Vector3d a_HitPos, eBlockFace a_HitFace) // Broadcast arrow hit sound m_World->BroadcastSoundEffect("entity.arrow.hit", m_HitBlockPos, 0.5f, static_cast(0.75 + (static_cast((GetUniqueID() * 23) % 32)) / 64)); + // Trigger any buttons that were hit + // Wooden buttons will be depressed by the arrow + cBlockButtonHandler::OnArrowHit(*m_World, m_HitBlockPos, a_HitFace); + if ((m_World->GetBlock(m_HitBlockPos) == E_BLOCK_TNT) && IsOnFire()) { m_World->SetBlock(m_HitBlockPos, E_BLOCK_AIR, 0); diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h index 9cb0f970a..15a9cc824 100644 --- a/src/Entities/Entity.h +++ b/src/Entities/Entity.h @@ -189,6 +189,7 @@ public: eEntityType GetEntityType(void) const { return m_EntityType; } + bool IsArrow (void) const { return IsA("cArrowEntity"); } bool IsEnderCrystal(void) const { return (m_EntityType == etEnderCrystal); } bool IsPlayer (void) const { return (m_EntityType == etPlayer); } bool IsPickup (void) const { return (m_EntityType == etPickup); } diff --git a/src/Simulator/IncrementalRedstoneSimulator/PressurePlateHandler.h b/src/Simulator/IncrementalRedstoneSimulator/PressurePlateHandler.h index 0944c5f8e..9f490b458 100644 --- a/src/Simulator/IncrementalRedstoneSimulator/PressurePlateHandler.h +++ b/src/Simulator/IncrementalRedstoneSimulator/PressurePlateHandler.h @@ -195,7 +195,7 @@ private: switch (a_BlockType) { case E_BLOCK_STONE_PRESSURE_PLATE: - return "block.wood_pressureplate.click_on"; + return "block.stone_pressureplate.click_on"; case E_BLOCK_WOODEN_PRESSURE_PLATE: return "block.wood_pressureplate.click_on"; case E_BLOCK_HEAVY_WEIGHTED_PRESSURE_PLATE: @@ -211,11 +211,11 @@ private: static AString GetClickOffSound(BLOCKTYPE a_BlockType) { - // manage on-sound + // manage off-sound switch (a_BlockType) { case E_BLOCK_STONE_PRESSURE_PLATE: - return "block.wood_pressureplate.click_off"; + return "block.stone_pressureplate.click_off"; case E_BLOCK_WOODEN_PRESSURE_PLATE: return "block.wood_pressureplate.click_off"; case E_BLOCK_HEAVY_WEIGHTED_PRESSURE_PLATE: