From f41321eb7b8b3346779fce8a1bb1f4badb671420 Mon Sep 17 00:00:00 2001 From: jadebenn Date: Sat, 20 Apr 2024 14:17:34 -0500 Subject: [PATCH] Use compile-time flag setting --- dCommon/Flag.h | 121 ++++++++++++++++++----- dGame/dComponents/PetComponent.cpp | 58 +++++------ dGame/dComponents/PetComponent.h | 4 +- dGame/dGameMessages/GameMessages.cpp | 8 +- dGame/dGameMessages/GameMessages.h | 2 +- dScripts/02_server/Pets/DamagingPets.cpp | 6 +- tests/dCommonTests/FlagTests.cpp | 61 +++++++++++- 7 files changed, 196 insertions(+), 64 deletions(-) diff --git a/dCommon/Flag.h b/dCommon/Flag.h index a26cbc69..d76725d0 100644 --- a/dCommon/Flag.h +++ b/dCommon/Flag.h @@ -1,81 +1,158 @@ #include "GeneralUtils.h" -#include -// TODO: Test bounds checking - +/** + * A bitset flag class capable of compile-time bounds-checking. + * It should be possible to unify its methods with C++23, but until then + * use the compile-time overloads preferentially. +*/ template requires std::is_enum_v class Flag { public: + // Type aliases using type = T; using underlying_type = std::underlying_type_t; - static constexpr auto MAX_FLAG_VAL = std::bit_width(sizeof(underlying_type)); + + // Static constants + static constexpr auto MAX_FLAG_VAL = sizeof(underlying_type) * CHAR_BIT; + + // Constructors + Flag() = default; + + // Run-time constructor + constexpr Flag(const T value) : m_Flags{ ConvertFlag(value) } {} /** - * Sets one or more flags + * RUNTIME: Sets one or more flags * @param flag Flag(s) to set */ template ... varArg> - constexpr void Set(varArg... flag) noexcept { + constexpr void Set(const varArg... flag) noexcept { m_Flags |= (ConvertFlag(flag) | ...); } /** - * Sets ONLY have the specified flag(s), clearing all others + * COMPILETIME: Sets one or more flags + * @param flag Flag(s) to set + */ + template + constexpr void Set() noexcept { + m_Flags |= (ConvertFlag() | ...); + } + + /** + * RUNTIME: Sets ONLY have the specified flag(s), clearing all others * @param flag Flag(s) to set exclusively */ template ... varArg> - constexpr void Reset(varArg... flag) { + constexpr void Reset(const varArg... flag) { m_Flags = (ConvertFlag(flag) | ...); } /** - * Unsets one or more flags + * COMPILETIME: Sets ONLY have the specified flag(s), clearing all others + * @param flag Flag(s) to set exclusively + */ + template + constexpr void Reset() noexcept { + m_Flags = (ConvertFlag() | ...); + } + + /** + * RUNTIME: Unsets one or more flags * @param flag Flag(s) to unset */ template ... varArg> - constexpr void Unset(varArg... flag) { + constexpr void Unset(const varArg... flag) { m_Flags &= ~(ConvertFlag(flag) | ...); } /** - * Returns true all the specified flag(s) are set + * COMPILETIME: Unsets one or more flags + * @param flag Flag(s) to unset + */ + template + constexpr void Unset() noexcept { + m_Flags &= ~(ConvertFlag() | ...); + } + + /** + * RUNTIME: Returns true all the specified flag(s) are set * @param flag Flag(s) to check */ template ... varArg> - constexpr bool Has(varArg... flag) const { + [[nodiscard]] + constexpr bool Has(const varArg... flag) const { return (m_Flags & (ConvertFlag(flag) | ...)) == (ConvertFlag(flag) | ...); } /** - * Returns true ONLY the specified flag(s) are set + * COMPILETIME: Returns true if all the specified flag(s) are set + * @param flag Flag(s) to check + */ + template + [[nodiscard]] + constexpr bool Has() const noexcept { + return (m_Flags & (ConvertFlag() | ...)) == (ConvertFlag() | ...); + } + + /** + * RUNTIME: Returns true if ONLY the specified flag(s) are set * @param flag Flag(s) to check */ template ... varArg> - constexpr bool HasOnly(varArg... flag) const { + [[nodiscard]] + constexpr bool HasOnly(const varArg... flag) const { return m_Flags == (ConvertFlag(flag) | ...); } + /** + * COMPILETIME: Returns true if ONLY the specified flag(s) are set + * @param flag Flag(s) to check + */ + template + [[nodiscard]] + constexpr bool HasOnly() const noexcept { + return m_Flags == (ConvertFlag() | ...); + } + + /** + * @return the raw value of the flags set + */ + [[nodiscard]] + constexpr T Value() const noexcept { + return static_cast(m_Flags); + } + /** * Operator overload to allow for '=' assignment */ constexpr Flag& operator=(const T value) { - Reset(value); + m_Flags = ConvertFlag(value); return *this; } private: + template + [[nodiscard]] + static consteval underlying_type ConvertFlag() noexcept { + constexpr auto flag_val = GeneralUtils::ToUnderlying(flag); + static_assert(flag_val <= MAX_FLAG_VAL, "Flag value is too large to set!"); + + return flag_val != 0 ? 1 << flag_val : flag_val; + } + [[nodiscard]] static constexpr underlying_type ConvertFlag(const T flag) { auto flag_val = GeneralUtils::ToUnderlying(flag); - if (flag_val != 0) { - return 1 << flag_val; + + // This is less-efficient than the compile-time check, but still works + // We can probably unify this and the above functions with C++23 and 'if consteval' + if (flag_val > MAX_FLAG_VAL) { + throw std::runtime_error{ "Flag value is too large to set!" }; } - // This should theoeretically be possible to do at compile time, but idk how - if (std::bit_width(flag_val) > MAX_FLAG_VAL) { - throw std::runtime_error{ "Enum value too large to be a flag!" }; - } - return flag_val; + + return flag_val != 0 ? 1 << flag_val : flag_val; } underlying_type m_Flags; diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 19013620..0c867e21 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -45,7 +45,7 @@ std::unordered_map PetComponent::activePets{}; * Maps all the pet lots to a flag indicating that the player has caught it. All basic pets have been guessed by ObjID * while the faction ones could be checked using their respective missions. */ -std::map PetComponent::petFlags{ +const std::map PetComponent::petFlags{ { 3050, 801 }, // Elephant { 3054, 803 }, // Cat { 3195, 806 }, // Triceratops @@ -75,10 +75,12 @@ std::map PetComponent::petFlags{ { 13067, 838 }, // Skeleton dragon }; -PetComponent::PetComponent(Entity* parentEntity, uint32_t componentId) : Component{ parentEntity } { +PetComponent::PetComponent(Entity* parentEntity, uint32_t componentId) + : Component{ parentEntity } + , m_Flags{ PetFlag::SPAWNING } +{ m_PetInfo = CDClientManager::GetTable()->GetByID(componentId); // TODO: Make reference when safe m_ComponentId = componentId; - m_Flags = PetFlag::SPAWNING; // Tameable m_StartPosition = m_Parent->GetPosition(); m_MovementAI = nullptr; @@ -99,7 +101,7 @@ void PetComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpd constexpr bool isDirty = true; outBitStream.Write(isDirty); // Always serialize as dirty for now - outBitStream.Write(m_Flags); + outBitStream.Write(m_Flags.Value()); outBitStream.Write(tamed ? m_Interaction.ability : ePetAbilityType::Invalid); // Something with the overhead icon? const bool interacting = m_Interaction.obj != LWOOBJID_EMPTY; @@ -205,12 +207,13 @@ void PetComponent::OnUse(Entity* originator) { buildFile = std::string(result.getStringField("ValidPiecesLXF")); - PuzzleData data; - data.buildFile = buildFile; - data.puzzleModelLot = result.getIntField("PuzzleModelLot"); - data.timeLimit = result.getFloatField("Timelimit"); - data.numValidPieces = result.getIntField("NumValidPieces"); - data.imaginationCost = result.getIntField("imagCostPerBuild"); + PuzzleData data{ + .puzzleModelLot = result.getIntField("PuzzleModelLot"), + .buildFile = buildFile, + .timeLimit = static_cast(result.getFloatField("Timelimit")), + .imaginationCost = result.getIntField("imagCostPerBuild"), + .numValidPieces = result.getIntField("NumValidPieces") + }; if (data.timeLimit <= 0) data.timeLimit = 60; imaginationCost = data.imaginationCost; @@ -306,7 +309,7 @@ void PetComponent::OnUse(Entity* originator) { GameMessages::SendNotifyPetTamingPuzzleSelected(originator->GetObjectID(), bricks, originator->GetSystemAddress()); m_Tamer = originator->GetObjectID(); - m_Flags.Set(PetFlag::IDLE, PetFlag::UNKNOWN4); + m_Flags.Set(); currentActivities.insert_or_assign(m_Tamer, m_Parent->GetObjectID()); @@ -334,7 +337,7 @@ void PetComponent::Update(float deltaTime) { ClientFailTamingMinigame(); // TODO: This is not despawning the built model correctly } - if (m_Flags.Has(PetFlag::SPAWNING)) OnSpawn(); + if (m_Flags.Has()) OnSpawn(); // Handle pet AI states switch (m_State) { @@ -501,7 +504,7 @@ void PetComponent::NotifyTamingBuildSuccess(const NiPoint3 position) { missionComponent->Progress(eMissionTaskType::PET_TAMING, m_Parent->GetLOT()); } - m_Flags.Reset(PetFlag::IDLE); + m_Flags.Reset(); auto* const characterComponent = tamer->GetComponent(); if (characterComponent != nullptr) { @@ -612,7 +615,7 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) { currentActivities.erase(m_Tamer); - m_Flags.Reset(PetFlag::TAMEABLE); + m_Flags.Reset(); m_Tamer = LWOOBJID_EMPTY; m_Timer = 0; @@ -661,7 +664,7 @@ void PetComponent::ClientFailTamingMinigame() { currentActivities.erase(m_Tamer); - m_Flags.Reset(PetFlag::TAMEABLE); + m_Flags.Reset(); m_Tamer = LWOOBJID_EMPTY; m_Timer = 0; @@ -721,15 +724,14 @@ void PetComponent::OnSpawn() { m_Parent->SetOwnerOverride(m_Owner); m_MovementAI->SetMaxSpeed(m_PetInfo.sprintSpeed); m_MovementAI->SetHaltDistance(m_FollowRadius); - //SetOnlyFlag(IDLE); //SetStatus(PetFlag::NONE); SetPetAiState(PetAiState::follow); } else { - m_Flags.Set(PetFlag::TAMEABLE); + m_Flags.Set(); SetPetAiState(PetAiState::idle); } - m_Flags.Set(PetFlag::IDLE); - m_Flags.Unset(PetFlag::SPAWNING); + m_Flags.Set(); + m_Flags.Unset(); Game::entityManager->SerializeEntity(m_Parent); } @@ -848,7 +850,7 @@ void PetComponent::StopInteract(bool bDontSerialize) { m_Interaction.type = PetInteractType::none; m_Interaction.ability = petAbility; SetPetAiState(PetAiState::follow); - m_Flags.Reset(PetFlag::IDLE); + m_Flags.Reset(); SetIsReadyToInteract(false); SetIsHandlingInteraction(false); // Needed? m_MovementAI->SetMaxSpeed(m_PetInfo.sprintSpeed); @@ -871,8 +873,8 @@ void PetComponent::SetupInteractBouncer() { constexpr auto petAbility = ePetAbilityType::JumpOnObject; m_Interaction.ability = petAbility; - m_Flags.Unset(PetFlag::IDLE); - m_Flags.Set(PetFlag::ON_SWITCH, PetFlag::NOT_WAITING); //SetStatus(PetFlag::NOT_WAITING); // TODO: Double-check this is the right flag being set + m_Flags.Unset(); + m_Flags.Set(); // TODO: Double-check this is the right flag being set LOG_DEBUG("m_Flags = %d", m_Flags); Game::entityManager->SerializeEntity(m_Parent); // TODO: Double-check pet packet captures @@ -913,8 +915,6 @@ void PetComponent::StartInteractBouncer() { SetIsHandlingInteraction(true); SwitchComponent* closestSwitch = SwitchComponent::GetClosestSwitch(m_MovementAI->GetDestination()); // TODO: Find a better way to do this closestSwitch->EntityEnter(m_Parent); - - //m_Timer += 0.5; } void PetComponent::HandleInteractBouncer() { @@ -954,8 +954,8 @@ void PetComponent::SetupInteractTreasureDig() { constexpr auto petAbility = ePetAbilityType::DigAtPosition; m_Interaction.ability = petAbility; - m_Flags.Unset(PetFlag::IDLE); - m_Flags.Set(PetFlag::ON_SWITCH, PetFlag::NOT_WAITING); // TODO: Double-check this is the right flag being set + m_Flags.Unset(); + m_Flags.Set(); // TODO: Double-check this is the right flag being set LOG_DEBUG("m_Flags = %d", m_Flags); Game::entityManager->SerializeEntity(m_Parent); // TODO: Double-check pet packet captures @@ -989,8 +989,8 @@ void PetComponent::StartInteractTreasureDig() { Game::entityManager->SerializeEntity(user); SetIsHandlingInteraction(true); - m_Flags.Unset(PetFlag::ON_SWITCH, PetFlag::NOT_WAITING); // TODO: FIND THE CORRECT STATUS TO USE HERE - m_Flags.Set(PetFlag::IDLE); + m_Flags.Unset(); // TODO: FIND THE CORRECT STATUS TO USE HERE + m_Flags.Set(); LOG_DEBUG("StartInteractTreasureDig() m_Flags = %d", m_Flags); Game::entityManager->SerializeEntity(m_Parent); @@ -1039,7 +1039,7 @@ void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) { // auto* const owner = GetOwner(); if (!owner) return; - m_Flags.Set(PetFlag::SPAWNING); + m_Flags.Set(); auto databaseData = inventoryComponent->GetDatabasePet(m_DatabaseId); diff --git a/dGame/dComponents/PetComponent.h b/dGame/dComponents/PetComponent.h index cdde1297..80abf0df 100644 --- a/dGame/dComponents/PetComponent.h +++ b/dGame/dComponents/PetComponent.h @@ -379,7 +379,7 @@ private: /** * The time limit to complete the build */ - int32_t timeLimit; + float timeLimit; /** * The imagination cost for the tamer to start the minigame @@ -432,7 +432,7 @@ private: /** * Flags that indicate that a player has tamed a pet, indexed by the LOT of the pet */ - static std::map petFlags; + static const std::map petFlags; /** * The halting radius of the pet while following a player TODO: Move into struct? diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 05a6af84..571e0fa5 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -523,14 +523,14 @@ void GameMessages::SendNotifyClientFlagChange(const LWOOBJID& objectID, uint32_t SEND_PACKET; } -void GameMessages::SendHelp(const LWOOBJID& objectId, const eHelpType help, const SystemAddress& sysAddr) { +void GameMessages::SendHelp(const LWOOBJID objectId, const eHelpType help, const SystemAddress& sysAddr) { CBITSTREAM; CMSGHEADER; - + bitStream.Write(objectId); bitStream.Write(eGameMessageType::HELP); bitStream.Write(help); - + SEND_PACKET; } @@ -1186,7 +1186,7 @@ void GameMessages::SendPlayerReachedRespawnCheckpoint(Entity* entity, const NiPo const bool bIsNotIdentity = rotation != NiQuaternionConstant::IDENTITY; bitStream.Write(bIsNotIdentity); - + if (bIsNotIdentity) { bitStream.Write(rotation.w); bitStream.Write(rotation.x); diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index c0c71b96..30019d0a 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -86,7 +86,7 @@ namespace GameMessages { void SendAddItemToInventoryClientSync(Entity* entity, const SystemAddress& sysAddr, Item* item, const LWOOBJID& objectID, bool showFlyingLoot, int itemCount, LWOOBJID subKey = LWOOBJID_EMPTY, eLootSourceType lootSourceType = eLootSourceType::NONE); void SendNotifyClientFlagChange(const LWOOBJID& objectID, uint32_t iFlagID, bool bFlag, const SystemAddress& sysAddr); - void SendHelp(const LWOOBJID& objectId, const eHelpType help, const SystemAddress& sysAddr); + void SendHelp(const LWOOBJID objectId, const eHelpType help, const SystemAddress& sysAddr); void SendChangeObjectWorldState(const LWOOBJID& objectID, eObjectWorldState state, const SystemAddress& sysAddr); void SendOfferMission(const LWOOBJID& entity, const SystemAddress& sysAddr, int32_t missionID, const LWOOBJID& offererID); diff --git a/dScripts/02_server/Pets/DamagingPets.cpp b/dScripts/02_server/Pets/DamagingPets.cpp index a289e261..ab4e30a4 100644 --- a/dScripts/02_server/Pets/DamagingPets.cpp +++ b/dScripts/02_server/Pets/DamagingPets.cpp @@ -77,10 +77,10 @@ void DamagingPets::MakeUntamable(Entity* self) { auto* const petComponent = self->GetComponent(); // If the pet is currently not being tamed, make it hostile - if (petComponent != nullptr && !petComponent->m_Flags.Has(PetFlag::NOT_WAITING)) { + if (petComponent != nullptr && !petComponent->m_Flags.Has()) { self->SetNetworkVar(u"bIAmTamable", false); self->SetVar(u"IsEvil", true); - petComponent->m_Flags.Set(PetFlag::IDLE); + petComponent->m_Flags.Set(); auto* combatAIComponent = self->GetComponent(); if (combatAIComponent != nullptr) { @@ -110,7 +110,7 @@ void DamagingPets::ClearEffects(Entity* self) { auto* petComponent = self->GetComponent(); if (petComponent != nullptr) { - petComponent->m_Flags.Set(PetFlag::TAMEABLE, PetFlag::UNKNOWN2); + petComponent->m_Flags.Set(); } auto* combatAIComponent = self->GetComponent(); diff --git a/tests/dCommonTests/FlagTests.cpp b/tests/dCommonTests/FlagTests.cpp index 0e9f5acd..49dca772 100644 --- a/tests/dCommonTests/FlagTests.cpp +++ b/tests/dCommonTests/FlagTests.cpp @@ -10,9 +10,65 @@ enum class TestFlag : uint8_t { }; /** - * Test bitset flags + * Test bitset flags (compile-time methods) */ -TEST(FlagTests, FlagMethodTest) { +TEST(FlagTests, FlagMethodTestCompileTime) { + using enum TestFlag; + + auto flag = Flag{}; + + // Test setting and reading single flags, exclusively + flag.Reset(); + ASSERT_TRUE(flag.HasOnly()); + flag.Reset(); + ASSERT_TRUE(flag.HasOnly()); + ASSERT_FALSE(flag.HasOnly()); + + // Test setting and reading multiple flags, exclusively + flag.Reset(); + ASSERT_FALSE(flag.Has()); + ASSERT_TRUE(flag.Has()); + ASSERT_TRUE(flag.Has()); + ASSERT_TRUE((flag.Has())); + ASSERT_FALSE(flag.HasOnly()); + ASSERT_FALSE(flag.HasOnly()); + ASSERT_TRUE((flag.HasOnly())); + + // Test flags are being properly reset for next batch of tests + flag.Reset(); + ASSERT_TRUE(flag.Has()); + ASSERT_TRUE(flag.HasOnly()); + ASSERT_FALSE(flag.Has()); + ASSERT_FALSE((flag.Has())); + + // Test setting and reading single flags, non-exclusively + flag.Set(); + ASSERT_TRUE(flag.Has()); + ASSERT_FALSE(flag.Has()); + + // Test setting and reading multiple flags, non-exclusively + flag.Set(); + ASSERT_TRUE((flag.Has())); + ASSERT_TRUE(flag.Has()); + ASSERT_FALSE((flag.Has())); + ASSERT_TRUE((flag.Has())); + ASSERT_FALSE(flag.Has()); + ASSERT_FALSE((flag.Has())); + + // Test unsetting and reading multiple flags, non-exclusively + flag.Unset(); + ASSERT_FALSE((flag.Has())); + ASSERT_FALSE(flag.Has()); + ASSERT_TRUE((flag.Has())); + ASSERT_FALSE((flag.Has())); + ASSERT_FALSE(flag.Has()); + ASSERT_FALSE((flag.Has())); +} + +/** + * Test bitset flags (run-time methods) +*/ +TEST(FlagTests, FlagMethodTestRunTime) { using enum TestFlag; auto flag = Flag{}; @@ -64,4 +120,3 @@ TEST(FlagTests, FlagMethodTest) { ASSERT_FALSE(flag.Has(FLAG1)); ASSERT_FALSE(flag.Has(FLAG1, FLAG3, FLAG2, FLAG4)); } -