From 258318ab98771f03d0109d7dfba81daaed26a2a0 Mon Sep 17 00:00:00 2001 From: DrButcher Date: Sun, 3 May 2020 22:05:32 +0200 Subject: Buttons can now be triggered by arrows. (#4670) * Buttons can now be triggered by arrows. --- src/Blocks/BlockButton.h | 133 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 115 insertions(+), 18 deletions(-) (limited to 'src/Blocks') 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 and returns if true if it is set */ + static bool IsButtonOn(NIBBLETYPE a_Meta) + { + 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: - /** Extracts the ON bit from metadata. */ - static bool IsButtonOn(NIBBLETYPE a_BlockMeta) + /** 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) { - return ((a_BlockMeta & 0x8) == 0x8); + 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 }; + } + } + } +} ; -- cgit v1.2.3