From 5b8033418f6548fe586d13f2630955006bf8b07a Mon Sep 17 00:00:00 2001 From: Emmanuel Jacyna Date: Fri, 5 Jul 2024 23:24:11 +1000 Subject: [PATCH] Issue #66 Handle door interactable (#197) --- bak/container.cpp | 1 + bak/container.hpp | 30 ++++++++----- bak/dialogSources.hpp | 2 + bak/gameState.cpp | 2 +- bak/lock.cpp | 18 ++++++++ bak/lock.hpp | 7 ++++ bak/save.cpp | 5 +++ bak/state/CMakeLists.txt | 1 + bak/state/door.cpp | 22 ++++++++++ bak/state/door.hpp | 15 +++++++ bak/types.hpp | 1 + game/gameRunner.hpp | 1 + game/interactable/door.hpp | 79 +++++++++++++++++++++++++++++------ game/interactable/factory.hpp | 3 +- 14 files changed, 161 insertions(+), 26 deletions(-) create mode 100644 bak/state/door.cpp create mode 100644 bak/state/door.hpp diff --git a/bak/container.cpp b/bak/container.cpp index cc0418bd..10646956 100644 --- a/bak/container.cpp +++ b/bak/container.cpp @@ -199,6 +199,7 @@ std::ostream& operator<<(std::ostream& os, const GenericContainer& gc) { os << "GenericContainer{ " << gc.GetHeader(); if (gc.HasLock()) os << gc.GetLock(); + if (gc.HasDoor()) os << gc.GetDoor(); if (gc.HasDialog()) os << gc.GetDialog(); if (gc.HasShop()) os << gc.GetShop(); if (gc.HasEncounter()) os << gc.GetEncounter(); diff --git a/bak/container.hpp b/bak/container.hpp index d86ecbc1..61e92277 100644 --- a/bak/container.hpp +++ b/bak/container.hpp @@ -24,7 +24,8 @@ enum class ContainerProperty HasDialog = 1, // 0x2 HasShop = 2, // 0x4 HasEncounter = 3, // 0x8 - HasTime = 4 // 0x10 + HasTime = 4, // 0x10 + HasDoor = 5 // 0x20 }; struct ContainerWorldLocationTag {}; @@ -93,6 +94,7 @@ class ContainerHeader bool HasShop() const { return CheckBitSet(mFlags, ContainerProperty::HasShop); } bool HasEncounter() const { return CheckBitSet(mFlags, ContainerProperty::HasEncounter); } bool HasTime() const { return CheckBitSet(mFlags, ContainerProperty::HasTime); } + bool HasDoor() const { return CheckBitSet(mFlags, ContainerProperty::HasDoor); } bool HasInventory() const { return mCapacity != 0; } std::uint32_t GetAddress() const { return mAddress; } @@ -136,6 +138,7 @@ class GenericContainer final : public IContainer { GenericContainer( ContainerHeader header, std::optional lock, + std::optional door, std::optional dialog, std::optional shop, std::optional encounter, @@ -144,6 +147,7 @@ class GenericContainer final : public IContainer { : mHeader{header}, mLock{lock}, + mDoor{door}, mDialog{dialog}, mShop{shop}, mEncounter{encounter}, @@ -167,6 +171,10 @@ class GenericContainer final : public IContainer { const ContainerDialog& GetDialog() const { ASSERT(mDialog); return *mDialog; } ContainerDialog& GetDialog() { ASSERT(mDialog); return *mDialog; } + bool HasDoor() const { return bool{mDoor}; } + DoorIndex GetDoor() const { ASSERT(mDoor); return mDoor->mDoorIndex; } + DoorIndex GetDoor() { ASSERT(mDoor); return mDoor->mDoorIndex; } + bool HasShop() const { return bool{mShop}; } ShopStats& GetShop() override { ASSERT(mShop); return *mShop; } const ShopStats& GetShop() const override { ASSERT(mShop); return *mShop; } @@ -215,6 +223,7 @@ class GenericContainer final : public IContainer { private: ContainerHeader mHeader; std::optional mLock; + std::optional mDoor; std::optional mDialog; std::optional mShop; std::optional mEncounter; @@ -228,6 +237,7 @@ GenericContainer LoadGenericContainer(FileBuffer& fb) auto header = ContainerHeader{HeaderTag{}, fb}; auto lockData = std::optional{}; + auto door = std::optional{}; auto dialog = std::optional{}; auto shopData = std::optional{}; auto encounter = std::optional{}; @@ -235,21 +245,18 @@ GenericContainer LoadGenericContainer(FileBuffer& fb) auto inventory = LoadInventory(fb, header.mItems, header.mCapacity); - if (header.mFlags == 0x21) - { - // This is not actually correct interpretation - // for this property type - const auto contextVar = fb.GetUint8(); - const auto dialogOrder = fb.GetUint8(); - const auto dialogKey = KeyTarget{fb.GetUint32LE()}; - dialog = ContainerDialog{contextVar, dialogOrder, dialogKey}; - } - else { if (header.HasLock()) { lockData = LoadLock(fb); } + + if (header.HasDoor()) + { + const auto doorIndex = fb.GetUint16LE(); + door = Door{DoorIndex{doorIndex}}; + } + if (header.HasDialog()) { const auto contextVar = fb.GetUint8(); @@ -299,6 +306,7 @@ GenericContainer LoadGenericContainer(FileBuffer& fb) return GenericContainer{ header, lockData, + door, dialog, shopData, encounter, diff --git a/bak/dialogSources.hpp b/bak/dialogSources.hpp index a3e3651e..ad1cc178 100644 --- a/bak/dialogSources.hpp +++ b/bak/dialogSources.hpp @@ -189,6 +189,7 @@ class DialogSources static constexpr auto mStump = KeyTarget{0xba}; static constexpr auto mTrappedAnimal = KeyTarget{0xab}; static constexpr auto mWell = KeyTarget{0xbc}; + static constexpr auto mDoorTooClose = KeyTarget{0x9d}; static constexpr auto mCharacterIsNotASpellcaster = KeyTarget{0xd8}; @@ -201,6 +202,7 @@ class DialogSources static constexpr auto mStartOfChapterActions = KeyTarget{0x1e8497}; static constexpr auto mDragonsBreath = 0xc7; + }; } diff --git a/bak/gameState.cpp b/bak/gameState.cpp index 301e0717..fa155b88 100644 --- a/bak/gameState.cpp +++ b/bak/gameState.cpp @@ -364,7 +364,7 @@ void GameState::SetDefaultDialogTextVariables() void GameState::SetDialogTextVariable(unsigned index, unsigned attribute) { - mLogger.Debug() << __FUNCTION__ << "(" << index << ", " << attribute << ")\n"; + mLogger.Spam() << __FUNCTION__ << "(" << index << ", " << attribute << ")\n"; assert(attribute > 0); // remember we always switch on attribute - 1... switch (attribute - 1) diff --git a/bak/lock.cpp b/bak/lock.cpp index f02dc974..d525ac30 100644 --- a/bak/lock.cpp +++ b/bak/lock.cpp @@ -14,6 +14,7 @@ std::ostream& operator<<(std::ostream& os, const LockStats& lock) return os; } + LockStats LoadLock(FileBuffer& fb) { const auto lockFlag = fb.GetUint8(); @@ -23,6 +24,11 @@ LockStats LoadLock(FileBuffer& fb) return LockStats{lockFlag, picklock, fairyChestIndex, damage}; } +std::ostream& operator<<(std::ostream& os, const Door& door) +{ + os << "Door { index: " << door.mDoorIndex << "}"; + return os; +} LockType ClassifyLock(unsigned lockRating) { @@ -36,6 +42,18 @@ LockType ClassifyLock(unsigned lockRating) return LockType::Unpickable; } +LockType ClassifyDoor(unsigned lockRating) +{ + if (lockRating < 50) + return LockType::Easy; + else if (lockRating < 80) + return LockType::Medium; + else if (lockRating < 100) + return LockType::Hard; + else + return LockType::Unpickable; +} + std::string_view ToString(LockType lock) { switch (lock) diff --git a/bak/lock.hpp b/bak/lock.hpp index e0fc3db6..5e35475d 100644 --- a/bak/lock.hpp +++ b/bak/lock.hpp @@ -40,6 +40,13 @@ struct FairyChest std::ostream& operator<<(std::ostream&, const LockStats&); +struct Door +{ + DoorIndex mDoorIndex; +}; + +std::ostream& operator<<(std::ostream&, const Door&); + // This is the lock "image" type enum class LockType { diff --git a/bak/save.cpp b/bak/save.cpp index 4b7f67f7..84434676 100644 --- a/bak/save.cpp +++ b/bak/save.cpp @@ -44,6 +44,11 @@ void Save(const GenericContainer& gc, FileBuffer& fb) fb.PutUint8(lock.mTrapDamage); } + if (gc.HasDoor()) + { + fb.Skip(2); // No need to write anything for doors + } + if (gc.HasDialog()) fb.Skip(6); // Don't need to write anything for dialog diff --git a/bak/state/CMakeLists.txt b/bak/state/CMakeLists.txt index 7dd0d685..222aea5b 100644 --- a/bak/state/CMakeLists.txt +++ b/bak/state/CMakeLists.txt @@ -1,6 +1,7 @@ add_library(bakState customStateChoice.hpp customStateChoice.cpp dialog.hpp dialog.cpp + door.hpp door.cpp encounter.hpp encounter.cpp event.hpp event.cpp item.hpp item.cpp diff --git a/bak/state/door.cpp b/bak/state/door.cpp new file mode 100644 index 00000000..e6a034c0 --- /dev/null +++ b/bak/state/door.cpp @@ -0,0 +1,22 @@ +#include "bak/state/door.hpp" + +#include "bak/state/event.hpp" +#include "bak/state/offsets.hpp" + +#include "bak/gameState.hpp" + +#include "com/logger.hpp" + +namespace BAK::State { + +bool GetDoorState(const GameState& gs, unsigned doorIndex) +{ + return gs.ReadEventBool(sDoorFlag + doorIndex); +} + +void SetDoorState(FileBuffer& fb, unsigned doorIndex, bool state) +{ + SetEventFlag(fb, sDoorFlag + doorIndex, state); +} + +} diff --git a/bak/state/door.hpp b/bak/state/door.hpp new file mode 100644 index 00000000..9ed71b12 --- /dev/null +++ b/bak/state/door.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "bak/file/fileBuffer.hpp" + +namespace BAK { +class GameState; +} + +namespace BAK::State { + +bool GetDoorState(const GameState&, unsigned doorIndex); +void SetDoorState(FileBuffer&, unsigned doorIndex, bool state); + +} + diff --git a/bak/types.hpp b/bak/types.hpp index 73dac22e..056b3717 100644 --- a/bak/types.hpp +++ b/bak/types.hpp @@ -10,6 +10,7 @@ using Chapter = Bounded, 1, 11>; using EntityIndex = StrongType; using ChoiceIndex = StrongType; using CombatantIndex = StrongType; +using DoorIndex = StrongType; using MonsterIndex = StrongType; using SpellIndex = StrongType; using SongIndex = std::uint16_t; diff --git a/game/gameRunner.hpp b/game/gameRunner.hpp index f46b89a8..ba7b5298 100644 --- a/game/gameRunner.hpp +++ b/game/gameRunner.hpp @@ -66,6 +66,7 @@ class GameRunner : public BAK::IZoneLoader std::nullopt, std::nullopt, std::nullopt, + std::nullopt, BAK::Inventory{0}}, mSystems{nullptr}, mSavedAngle{0}, diff --git a/game/interactable/door.hpp b/game/interactable/door.hpp index f446e890..9065cffa 100644 --- a/game/interactable/door.hpp +++ b/game/interactable/door.hpp @@ -2,21 +2,25 @@ #include "game/interactable/IInteractable.hpp" +#include "bak/dialogTarget.hpp" +#include "bak/state/door.hpp" + #include "bak/IContainer.hpp" #include "bak/container.hpp" #include "bak/dialog.hpp" #include "bak/dialogSources.hpp" #include "bak/gameState.hpp" -#include "bak/itemNumbers.hpp" + +#include "graphics/glm.hpp" #include "gui/IDialogScene.hpp" #include "gui/IGuiManager.hpp" +#include + + namespace Game::Interactable { -/* - * These containers have an optional dialog and inventory encounter - */ class Door : public IInteractable { private: @@ -24,29 +28,81 @@ class Door : public IInteractable public: Door( Gui::IGuiManager& guiManager, - BAK::Target target, - const EncounterCallback& encounterCallback) + BAK::GameState& gameState) : mGuiManager{guiManager}, + mGameState{gameState}, mDialogScene{ []{}, []{}, [&](const auto& choice){ DialogFinished(choice); }}, - mDefaultDialog{target}, - mContainer{nullptr}, - mEncounterCallback{encounterCallback} + mContainer{nullptr} {} void BeginInteraction(BAK::GenericContainer& container, BAK::EntityType) override { mContainer = &container; - StartDialog(mDefaultDialog); + assert(mContainer->HasDoor()); + const auto doorIndex = mContainer->GetDoor(); + const auto doorState = BAK::State::GetDoorState(mGameState, doorIndex.mValue); + Logging::LogInfo("Door") << "DoorIndex: " << doorIndex << " DoorOpen? " << std::boolalpha << doorState << " locked? " << (mContainer->HasLock() ? mContainer->GetLock().mRating : 0) << "\n"; + + const auto playerPos = glm::cast(mGameState.GetLocation().mPosition); + const auto doorPos = glm::cast(container.GetHeader().GetPosition()); + if (glm::distance(playerPos, doorPos) < 800) + { + StartDialog(BAK::DialogSources::mDoorTooClose); + } + else if (doorState) + { + // Door opened, can always close it + CloseDoor(); + } + else if (mContainer->HasLock() && mContainer->GetLock().mRating > 0) + { + mGameState.SetDialogContext_7530(1); + StartDialog(BAK::DialogSources::mChooseUnlock); + } + else + { + OpenDoor(); + } } void DialogFinished(const std::optional& choice) { ASSERT(mContainer); + ASSERT(choice); + + if (choice->mValue == BAK::Keywords::sYesIndex) + { + mGuiManager.ShowLock( + mContainer, + [this]{ LockFinished(); }); + } + } + + void LockFinished() + { + if (mGuiManager.IsLockOpened()) + { + OpenDoor(); + } + } + + void OpenDoor() + { + const auto doorIndex = mContainer->GetDoor().mValue; + mGameState.Apply(BAK::State::SetDoorState, doorIndex, true); + Logging::LogInfo(__FUNCTION__) << " index; " << doorIndex << "\n"; + } + + void CloseDoor() + { + const auto doorIndex = mContainer->GetDoor().mValue; + mGameState.Apply(BAK::State::SetDoorState, doorIndex, false); + Logging::LogInfo(__FUNCTION__) << " index; " << doorIndex << "\n"; } void EncounterFinished() override @@ -64,10 +120,9 @@ class Door : public IInteractable private: Gui::IGuiManager& mGuiManager; + BAK::GameState& mGameState; Gui::DynamicDialogScene mDialogScene; - BAK::Target mDefaultDialog; BAK::GenericContainer* mContainer; - const EncounterCallback& mEncounterCallback; }; } diff --git a/game/interactable/factory.hpp b/game/interactable/factory.hpp index 2947f8d6..f05c14d9 100644 --- a/game/interactable/factory.hpp +++ b/game/interactable/factory.hpp @@ -113,8 +113,7 @@ class InteractableFactory case InteractableType::Door: return std::make_unique( mGuiManager, - BAK::DialogSources::mUnknownObject, - [](auto){}); + mGameState); case InteractableType::Corn: return MakeGeneric(BAK::DialogSources::mCorn); case InteractableType::CrystalTree: