diff --git a/app/main3d.cpp b/app/main3d.cpp index f76058b..acaa1a6 100644 --- a/app/main3d.cpp +++ b/app/main3d.cpp @@ -264,10 +264,7 @@ int main(int argc, char** argv) { bool guiHandled = root.OnMouseEvent( Gui::LeftMousePress{guiScaleInv * clickPos}); - // i.e. only MainView is present - // should really formalise this with some sort of - // GuiManager state, interacting with 2d or 3d world..? - if (!guiHandled && guiManager.mScreenStack.size() == 1) + if (!guiHandled && guiManager.InMainView()) { glDisable(GL_BLEND); glDisable(GL_MULTISAMPLE); diff --git a/bak/chapterTransitions.cpp b/bak/chapterTransitions.cpp index 85e2aa0..c5abd67 100644 --- a/bak/chapterTransitions.cpp +++ b/bak/chapterTransitions.cpp @@ -39,7 +39,7 @@ void TransitionToChapter(Chapter chapter, GameState& gs) } const auto startLocation = LoadChapterStartLocation(chapter); gs.SetLocation(startLocation.mLocation); - // gs.SetMapLocation(startLocation.mMapLocation); + gs.SetMapLocation(startLocation.mMapLocation); // mov eax, [bp+currentGold] // add partyGold, eax @@ -55,8 +55,6 @@ void TransitionToChapter(Chapter chapter, GameState& gs) gs.Apply(State::SetEventFlagFalse, 0x190 + i); } - //gs.ClearActiveSpells(); - switch (chapter.mValue) { case 1:[[fallthrough]]; diff --git a/bak/character.cpp b/bak/character.cpp index b46f9a3..7d56211 100644 --- a/bak/character.cpp +++ b/bak/character.cpp @@ -6,16 +6,452 @@ namespace BAK { +Character::Character( + unsigned index, + const std::string& name, + const Skills& skills, + Spells spells, + const std::array& unknown, + const std::array& unknown2, + const Conditions& conditions, + Inventory&& inventory) +: + mCharacterIndex{index}, + mName{name}, + mSkills{skills}, + mSpells{spells}, + mUnknown{unknown}, + mUnknown2{unknown2}, + mConditions{conditions}, + mInventory{std::move(inventory)}, + mSkillAffectors{}, + mLogger{Logging::LogState::GetLogger("BAK::Character")} +{} + +/* IContainer */ + +Inventory& Character::GetInventory() { return mInventory; } +const Inventory& Character::GetInventory() const { return mInventory; } + +bool Character::CanSwapItem(const InventoryItem& ref) const +{ + return (ref.IsItemType(BAK::ItemType::Sword) && IsSwordsman()) + || (ref.IsItemType(BAK::ItemType::Staff) && IsSpellcaster()); +} + +bool Character::CanAddItem(const InventoryItem& ref) const +{ + auto item = ref; + const auto itemIndex = ref.GetItemIndex(); + // Day's rations are equivalent to 1 of normal rations + if (itemIndex == BAK::sDayRations) + { + item = InventoryItemFactory::MakeItem(BAK::sRations, 1); + } + + if (mInventory.CanAddCharacter(item) > 0) + return true; + else if (item.IsItemType(ItemType::Staff) + && HasEmptyStaffSlot()) + return true; + else if (item.IsItemType(ItemType::Sword) + && HasEmptySwordSlot()) + return true; + else if (item.IsItemType(ItemType::Crossbow) + && HasEmptyCrossbowSlot()) + return true; + else if (item.IsItemType(ItemType::Armor) + && HasEmptyArmorSlot()) + return true; + else + return false; +} + +bool Character::GiveItem(const InventoryItem& ref) +{ + auto item = ref; + const auto itemIndex = ref.GetItemIndex(); + + bool equipped = false; + if (CanReplaceEquippableItem(item.GetObject().mType) + && mInventory.FindEquipped(item.GetObject().mType) + == mInventory.GetItems().end()) + { + item.SetEquipped(true); + equipped = true; + } + else + { + item.SetEquipped(false); + } + + // Day's rations are equivalent to 1 of normal rations + if (itemIndex == BAK::sDayRations) + { + item = InventoryItemFactory::MakeItem(BAK::sRations, 1); + } + // Quegian brandy + else if (itemIndex == BAK::sBrandy + || itemIndex == BAK::sAle + || itemIndex == BAK::sKeshianAle) + { + mConditions.IncreaseCondition( + static_cast(ref.GetObject().mEffectMask), + ref.GetObject().mEffect); + return true; + } + + if (item.GetItemIndex() == sRations + && GetConditions().GetCondition(Condition::Starving).Get() > 0) + { + AdjustCondition(Condition::Starving, -100); + item.SetQuantity(item.GetQuantity() - 1); + if (item.GetQuantity() == 0) + { + return true; + } + } + const auto stackSize = item.GetObject().mStackSize; + + // Split into stacks if necessary + std::vector items{}; + if (item.IsStackable() + && item.GetQuantity() > stackSize) + { + const auto nStacks = item.GetQuantity() / stackSize; + for (unsigned i = 0; i < nStacks; i++) + { + items.emplace_back( + InventoryItemFactory::MakeItem( + ItemIndex{itemIndex}, + stackSize)); + } + const auto remainder = item.GetQuantity() % stackSize; + if (remainder != 0) + items.emplace_back( + InventoryItemFactory::MakeItem( + ItemIndex{itemIndex}, + remainder)); + } + else + { + items.emplace_back(item); + } + + bool added = false; + for (const auto& item : items) + { + if (mInventory.CanAddCharacter(item) || equipped) + { + mInventory.AddItem(item); + added = true; + } + } + + return added; +} + +bool Character::RemoveItem(const InventoryItem& item) +{ + if (mInventory.HaveItem(item)) + { + mInventory.RemoveItem(item); + return true; + } + + return false; +} + +ContainerType Character::GetContainerType() const +{ + return ContainerType::Inv; +} + +ShopStats& Character::GetShop() { ASSERT(false); return *reinterpret_cast(this);} +const ShopStats& Character::GetShop() const { ASSERT(false); return *reinterpret_cast(this);} +LockStats& Character::GetLock() { ASSERT(false); return *reinterpret_cast(this); } + +/* Character Getters */ + +CharIndex Character::GetIndex() const { return mCharacterIndex; } + +bool Character::IsSpellcaster() const { return mSkills.GetSkill(BAK::SkillType::Casting).mMax != 0; } +bool Character::IsSwordsman() const { return !IsSpellcaster(); } + +bool Character::HasEmptyStaffSlot() const +{ + return IsSpellcaster() + && mInventory.FindEquipped(ItemType::Staff) + == mInventory.GetItems().end(); +} + +bool Character::HasEmptySwordSlot() const +{ + return IsSwordsman() + && mInventory.FindEquipped(ItemType::Sword) + == mInventory.GetItems().end(); +} + +bool Character::HasEmptyCrossbowSlot() const +{ + return IsSwordsman() + && mInventory.FindEquipped(ItemType::Crossbow) + == mInventory.GetItems().end(); +} + +bool Character::HasEmptyArmorSlot() const +{ + return mInventory.FindEquipped(ItemType::Armor) + == mInventory.GetItems().end(); +} + +InventoryIndex Character::GetItemAtSlot(ItemType slot) const +{ + auto it = mInventory.FindEquipped(slot) ; + const auto index = mInventory.GetIndexFromIt(it); + assert(index); + return *index; +} + +ItemType Character::GetWeaponType() const +{ + if (IsSpellcaster()) + return ItemType::Staff; + else + return ItemType::Sword; +} + +bool Character::CanReplaceEquippableItem(ItemType type) const +{ + if (IsSpellcaster() && type == ItemType::Staff) + return true; + else if (IsSwordsman() && (type == ItemType::Sword + || type == ItemType::Crossbow)) + return true; + else if (type == ItemType::Armor) + return true; + return false; +} + +void Character::ApplyItemToSlot(InventoryIndex index, ItemType slot) +{ + auto& item = mInventory.GetAtIndex(index); + auto equipped = mInventory.FindEquipped(slot); + + if (equipped == mInventory.GetItems().end()) + { + item.SetEquipped(true); + return; + } + + const auto slotIndex = *mInventory.GetIndexFromIt(equipped); + // We are trying to move this item onto itself + if (slotIndex == index) + { + return; + } + + if (item.IsItemType(slot)) + { + item.SetEquipped(true); + if (equipped != mInventory.GetItems().end()) + equipped->SetEquipped(false); + } + else + { + // Try use item at index on slot item + //UseItem(index, slotIndex); + } + + Logging::LogDebug("CharacterAFMove") << __FUNCTION__ << " " << item << " " << BAK::ToString(slot) << "\n"; + if (equipped != mInventory.GetItems().end()) + Logging::LogDebug("CharacterAFEquip") << __FUNCTION__ << " " << *equipped << " " << equipped->IsEquipped() << " " << BAK::ToString(slot) << "\n"; +} + +void Character::CheckPostConditions() +{ + if (IsSpellcaster()) + { + ASSERT(GetInventory().FindEquipped(ItemType::Sword) + == GetInventory().GetItems().end()); + ASSERT(GetInventory().FindEquipped(ItemType::Crossbow) + == GetInventory().GetItems().end()); + + unsigned staffCount = 0; + for (const auto& item : GetInventory().GetItems()) + { + if (item.IsItemType(ItemType::Staff) && item.IsEquipped()) + { + staffCount++; + } + } + ASSERT(staffCount <= 1); + } + else + { + ASSERT(GetInventory().FindEquipped(ItemType::Staff) + == GetInventory().GetItems().end()); + + unsigned swordCount = 0; + for (const auto& item : GetInventory().GetItems()) + { + if (item.IsItemType(ItemType::Sword) && item.IsEquipped()) + { + swordCount++; + } + } + ASSERT(swordCount <= 1); + + unsigned crossbowCount = 0; + for (const auto& item : GetInventory().GetItems()) + { + if (item.IsItemType(ItemType::Crossbow) && item.IsEquipped()) + { + crossbowCount++; + } + } + + ASSERT(crossbowCount <= 1); + } +} + +unsigned Character::GetTotalItem(const std::vector& itemIndex) +{ + unsigned total = 0; + for (const auto& item : GetInventory().GetItems()) + { + if (std::find_if(itemIndex.begin(), itemIndex.end(), [&item](auto& elem){ return item.GetItemIndex() == elem;}) != itemIndex.end()) + { + if (item.IsConditionBased() || item.IsChargeBased()) + { + total += 1; + } + else + { + total += item.GetQuantity(); + } + } + } + return total; +} + +const std::string& Character::GetName() const +{ + return mName; +} + +bool Character::CanHeal(bool isInn) +{ + const auto multiplier = isInn ? 1.0 : .80; + const auto health = GetSkill(BAK::SkillType::TotalHealth); + const auto maxHealth = GetMaxSkill(BAK::SkillType::TotalHealth); + return health < (maxHealth * multiplier); +} + +bool Character::HaveNegativeCondition() +{ + for (unsigned i = 0; i < Conditions::sNumConditions; i++) + { + auto cond = static_cast(i); + if (cond == BAK::Condition::Healing) continue; + if (mConditions.GetCondition(cond).Get() > 0) return true; + } + return false; +} + +const Skills& Character::GetSkills() const +{ + return mSkills; +} + +Skills& Character::GetSkills() +{ + return mSkills; +} + +void Character::ImproveSkill(SkillType skill, SkillChange skillChangeType, int multiplier) +{ + mSkills.ImproveSkill(mConditions, skill, skillChangeType, multiplier); + UpdateSkills(); +} + +unsigned Character::GetSkill(SkillType skill) const +{ + if (skill != SkillType::TotalHealth) + { + mSkills.GetSkill(skill).mModifier = mInventory.CalculateModifiers(skill); + } + return CalculateEffectiveSkillValue( + skill, + mSkills, + mConditions, + mSkillAffectors, + SkillRead::Current); +} + +unsigned Character::GetMaxSkill(SkillType skill) const +{ + if (skill != SkillType::TotalHealth) + { + mSkills.GetSkill(skill).mModifier = mInventory.CalculateModifiers(skill); + } + return CalculateEffectiveSkillValue( + skill, + mSkills, + mConditions, + mSkillAffectors, + SkillRead::MaxSkill); +} + +void Character::AdjustCondition(BAK::Condition cond, signed amount) +{ + mConditions.AdjustCondition(mSkills, cond, amount); +} + +const Conditions& Character::GetConditions() const { return mConditions; } +Conditions& Character::GetConditions() { return mConditions; } + +void Character::UpdateSkills() +{ + for (unsigned i = 0; i < BAK::Skills::sSkills; i++) + GetSkill(static_cast(i)); +} + +Spells& Character::GetSpells() +{ + return mSpells; +} + +const Spells& Character::GetSpells() const +{ + return mSpells; +} + +void Character::AddSkillAffector(const SkillAffector& affector) +{ + mSkillAffectors.emplace_back(affector); +} + +std::vector& Character::GetSkillAffectors() +{ + return mSkillAffectors; +} + +const std::vector& Character::GetSkillAffectors() const +{ + return mSkillAffectors; +} + std::ostream& operator<<(std::ostream& os, const Character& c) { - os << "Character [" << c.mName << " Skills: \n" << c.mSkills; - os << "Spells: " << std::hex << c.mSpells << std::dec << "\n"; - os << "Unknown: " << std::hex << c.mUnknown << std::dec << "\n"; - os << "Unknown2: " << std::hex << c.mUnknown2 << std::dec << "\n"; - os << c.mConditions << "\n"; - os << "SkillAffectors: " << c.mSkillAffectors << "\n"; - os << "Inventory: " << c.mInventory << "\n"; - return os; +os << "Character [" << c.mName << " Skills: \n" << c.mSkills; +os << "Spells: " << std::hex << c.mSpells << std::dec << "\n"; +os << "Unknown: " << std::hex << c.mUnknown << std::dec << "\n"; +os << "Unknown2: " << std::hex << c.mUnknown2 << std::dec << "\n"; +os << c.mConditions << "\n"; +os << "SkillAffectors: " << c.mSkillAffectors << "\n"; +os << "Inventory: " << c.mInventory << "\n"; +return os; } } diff --git a/bak/character.hpp b/bak/character.hpp index 36e6dc9..c3c02a0 100644 --- a/bak/character.hpp +++ b/bak/character.hpp @@ -30,432 +30,60 @@ class Character final : public IContainer const std::array& unknown, const std::array& unknown2, const Conditions& conditions, - Inventory&& inventory) - : - mCharacterIndex{index}, - mName{name}, - mSkills{skills}, - mSpells{spells}, - mUnknown{unknown}, - mUnknown2{unknown2}, - mConditions{conditions}, - mInventory{std::move(inventory)}, - mSkillAffectors{}, - mLogger{Logging::LogState::GetLogger("BAK::Character")} - {} + Inventory&& inventory); /* IContainer */ + Inventory& GetInventory() override; + const Inventory& GetInventory() const override; + bool CanAddItem(const InventoryItem& ref) const override; + bool GiveItem(const InventoryItem& ref) override; + bool RemoveItem(const InventoryItem& item) override; + ContainerType GetContainerType() const override; + ShopStats& GetShop() override; + const ShopStats& GetShop() const override; + LockStats& GetLock() override; - Inventory& GetInventory() override { return mInventory; } - const Inventory& GetInventory() const override { return mInventory; } - - bool CanSwapItem(const InventoryItem& ref) const - { - return (ref.IsItemType(BAK::ItemType::Sword) && IsSwordsman()) - || (ref.IsItemType(BAK::ItemType::Staff) && IsSpellcaster()); - } - - bool CanAddItem(const InventoryItem& ref) const override - { - auto item = ref; - const auto itemIndex = ref.GetItemIndex(); - // Day's rations are equivalent to 1 of normal rations - if (itemIndex == BAK::sDayRations) - { - item = InventoryItemFactory::MakeItem(BAK::sRations, 1); - } - - if (mInventory.CanAddCharacter(item) > 0) - return true; - else if (item.IsItemType(ItemType::Staff) - && HasEmptyStaffSlot()) - return true; - else if (item.IsItemType(ItemType::Sword) - && HasEmptySwordSlot()) - return true; - else if (item.IsItemType(ItemType::Crossbow) - && HasEmptyCrossbowSlot()) - return true; - else if (item.IsItemType(ItemType::Armor) - && HasEmptyArmorSlot()) - return true; - else - return false; - } - - bool GiveItem(const InventoryItem& ref) override - { - auto item = ref; - const auto itemIndex = ref.GetItemIndex(); - - bool equipped = false; - if (CanReplaceEquippableItem(item.GetObject().mType) - && mInventory.FindEquipped(item.GetObject().mType) - == mInventory.GetItems().end()) - { - item.SetEquipped(true); - equipped = true; - } - else - { - item.SetEquipped(false); - } - - // Day's rations are equivalent to 1 of normal rations - if (itemIndex == BAK::sDayRations) - { - item = InventoryItemFactory::MakeItem(BAK::sRations, 1); - } - // Quegian brandy - else if (itemIndex == BAK::sBrandy - || itemIndex == BAK::sAle - || itemIndex == BAK::sKeshianAle) - { - mConditions.IncreaseCondition( - static_cast(ref.GetObject().mEffectMask), - ref.GetObject().mEffect); - return true; - } - - if (item.GetItemIndex() == sRations - && GetConditions().GetCondition(Condition::Starving).Get() > 0) - { - AdjustCondition(Condition::Starving, -100); - item.SetQuantity(item.GetQuantity() - 1); - if (item.GetQuantity() == 0) - { - return true; - } - } - const auto stackSize = item.GetObject().mStackSize; - - // Split into stacks if necessary - std::vector items{}; - if (item.IsStackable() - && item.GetQuantity() > stackSize) - { - const auto nStacks = item.GetQuantity() / stackSize; - for (unsigned i = 0; i < nStacks; i++) - { - items.emplace_back( - InventoryItemFactory::MakeItem( - ItemIndex{itemIndex}, - stackSize)); - } - const auto remainder = item.GetQuantity() % stackSize; - if (remainder != 0) - items.emplace_back( - InventoryItemFactory::MakeItem( - ItemIndex{itemIndex}, - remainder)); - } - else - { - items.emplace_back(item); - } - - bool added = false; - for (const auto& item : items) - { - if (mInventory.CanAddCharacter(item) || equipped) - { - mInventory.AddItem(item); - added = true; - } - } - - return added; - } - - bool RemoveItem(const InventoryItem& item) override - { - if (mInventory.HaveItem(item)) - { - mInventory.RemoveItem(item); - return true; - } - - return false; - } - - ContainerType GetContainerType() const override - { - return ContainerType::Inv; - } - - ShopStats& GetShop() override { ASSERT(false); return *reinterpret_cast(this);} - const ShopStats& GetShop() const override { ASSERT(false); return *reinterpret_cast(this);} - LockStats& GetLock() override { ASSERT(false); return *reinterpret_cast(this); } /* Character Getters */ - - CharIndex GetIndex() const { return mCharacterIndex; } - - bool IsSpellcaster() const { return mSkills.GetSkill(BAK::SkillType::Casting).mMax != 0; } - bool IsSwordsman() const { return !IsSpellcaster(); } - - bool HasEmptyStaffSlot() const - { - return IsSpellcaster() - && mInventory.FindEquipped(ItemType::Staff) - == mInventory.GetItems().end(); - } - - bool HasEmptySwordSlot() const - { - return IsSwordsman() - && mInventory.FindEquipped(ItemType::Sword) - == mInventory.GetItems().end(); - } - - bool HasEmptyCrossbowSlot() const - { - return IsSwordsman() - && mInventory.FindEquipped(ItemType::Crossbow) - == mInventory.GetItems().end(); - } - - bool HasEmptyArmorSlot() const - { - return mInventory.FindEquipped(ItemType::Armor) - == mInventory.GetItems().end(); - } - - InventoryIndex GetItemAtSlot(ItemType slot) const - { - auto it = mInventory.FindEquipped(slot) ; - const auto index = mInventory.GetIndexFromIt(it); - assert(index); - return *index; - } - - ItemType GetWeaponType() const - { - if (IsSpellcaster()) - return ItemType::Staff; - else - return ItemType::Sword; - } - - bool CanReplaceEquippableItem(ItemType type) const - { - if (IsSpellcaster() && type == ItemType::Staff) - return true; - else if (IsSwordsman() && (type == ItemType::Sword - || type == ItemType::Crossbow)) - return true; - else if (type == ItemType::Armor) - return true; - return false; - } - - void ApplyItemToSlot(InventoryIndex index, ItemType slot) - { - auto& item = mInventory.GetAtIndex(index); - auto equipped = mInventory.FindEquipped(slot); - - if (equipped == mInventory.GetItems().end()) - { - item.SetEquipped(true); - return; - } - - const auto slotIndex = *mInventory.GetIndexFromIt(equipped); - // We are trying to move this item onto itself - if (slotIndex == index) - { - return; - } - - if (item.IsItemType(slot)) - { - item.SetEquipped(true); - if (equipped != mInventory.GetItems().end()) - equipped->SetEquipped(false); - } - else - { - // Try use item at index on slot item - //UseItem(index, slotIndex); - } - - Logging::LogDebug("CharacterAFMove") << __FUNCTION__ << " " << item << " " << BAK::ToString(slot) << "\n"; - if (equipped != mInventory.GetItems().end()) - Logging::LogDebug("CharacterAFEquip") << __FUNCTION__ << " " << *equipped << " " << equipped->IsEquipped() << " " << BAK::ToString(slot) << "\n"; - } - - void CheckPostConditions() - { - if (IsSpellcaster()) - { - ASSERT(GetInventory().FindEquipped(ItemType::Sword) - == GetInventory().GetItems().end()); - ASSERT(GetInventory().FindEquipped(ItemType::Crossbow) - == GetInventory().GetItems().end()); - - unsigned staffCount = 0; - for (const auto& item : GetInventory().GetItems()) - { - if (item.IsItemType(ItemType::Staff) && item.IsEquipped()) - { - staffCount++; - } - } - ASSERT(staffCount <= 1); - } - else - { - ASSERT(GetInventory().FindEquipped(ItemType::Staff) - == GetInventory().GetItems().end()); - - unsigned swordCount = 0; - for (const auto& item : GetInventory().GetItems()) - { - if (item.IsItemType(ItemType::Sword) && item.IsEquipped()) - { - swordCount++; - } - } - ASSERT(swordCount <= 1); - - unsigned crossbowCount = 0; - for (const auto& item : GetInventory().GetItems()) - { - if (item.IsItemType(ItemType::Crossbow) && item.IsEquipped()) - { - crossbowCount++; - } - } - - ASSERT(crossbowCount <= 1); - } - } - - unsigned GetTotalItem(const std::vector& itemIndex) - { - unsigned total = 0; - for (const auto& item : GetInventory().GetItems()) - { - if (std::find_if(itemIndex.begin(), itemIndex.end(), [&item](auto& elem){ return item.GetItemIndex() == elem;}) != itemIndex.end()) - { - if (item.IsConditionBased() || item.IsChargeBased()) - { - total += 1; - } - else - { - total += item.GetQuantity(); - } - } - } - return total; - } - - const std::string& GetName() const - { - return mName; - } - - bool CanHeal(bool isInn) - { - const auto multiplier = isInn ? 1.0 : .80; - const auto health = GetSkill(BAK::SkillType::TotalHealth); - const auto maxHealth = GetMaxSkill(BAK::SkillType::TotalHealth); - return health < (maxHealth * multiplier); - } - - bool HaveNegativeCondition() - { - for (unsigned i = 0; i < Conditions::sNumConditions; i++) - { - auto cond = static_cast(i); - if (cond == BAK::Condition::Healing) continue; - if (mConditions.GetCondition(cond).Get() > 0) return true; - } - return false; - } - - const Skills& GetSkills() const - { - return mSkills; - } - - Skills& GetSkills() - { - return mSkills; - } - - void ImproveSkill(SkillType skill, SkillChange skillChangeType, int multiplier) - { - mSkills.ImproveSkill(mConditions, skill, skillChangeType, multiplier); - UpdateSkills(); - } - - unsigned GetSkill(SkillType skill) const - { - if (skill != SkillType::TotalHealth) - { - mSkills.GetSkill(skill).mModifier = mInventory.CalculateModifiers(skill); - } - return CalculateEffectiveSkillValue( - skill, - mSkills, - mConditions, - mSkillAffectors, - SkillRead::Current); - } - - unsigned GetMaxSkill(SkillType skill) const - { - if (skill != SkillType::TotalHealth) - { - mSkills.GetSkill(skill).mModifier = mInventory.CalculateModifiers(skill); - } - return CalculateEffectiveSkillValue( - skill, - mSkills, - mConditions, - mSkillAffectors, - SkillRead::MaxSkill); - } - - void AdjustCondition(BAK::Condition cond, signed amount) - { - mConditions.AdjustCondition(mSkills, cond, amount); - } - - const Conditions& GetConditions() const { return mConditions; } - Conditions& GetConditions() { return mConditions; } - - void UpdateSkills() - { - for (unsigned i = 0; i < BAK::Skills::sSkills; i++) - GetSkill(static_cast(i)); - } - - Spells& GetSpells() - { - return mSpells; - } - - const Spells& GetSpells() const - { - return mSpells; - } - - void AddSkillAffector(const SkillAffector& affector) - { - mSkillAffectors.emplace_back(affector); - } - - std::vector& GetSkillAffectors() - { - return mSkillAffectors; - } - - const std::vector& GetSkillAffectors() const - { - return mSkillAffectors; - } + bool CanSwapItem(const InventoryItem& ref) const; + + CharIndex GetIndex() const; + + bool IsSpellcaster() const; + bool IsSwordsman() const; + + bool HasEmptyStaffSlot() const; + bool HasEmptySwordSlot() const; + bool HasEmptyCrossbowSlot() const; + bool HasEmptyArmorSlot() const; + InventoryIndex GetItemAtSlot(ItemType slot) const; + ItemType GetWeaponType() const; + bool CanReplaceEquippableItem(ItemType type) const; + void ApplyItemToSlot(InventoryIndex index, ItemType slot); + void CheckPostConditions(); + unsigned GetTotalItem(const std::vector& itemIndex); + const std::string& GetName() const; + + bool CanHeal(bool isInn); + bool HaveNegativeCondition(); + + const Skills& GetSkills() const; + Skills& GetSkills(); + + void ImproveSkill(SkillType skill, SkillChange skillChangeType, int multiplier); + unsigned GetSkill(SkillType skill) const; + unsigned GetMaxSkill(SkillType skill) const; + void AdjustCondition(BAK::Condition cond, signed amount); + const Conditions& GetConditions() const; + Conditions& GetConditions(); + + void UpdateSkills(); + + Spells& GetSpells(); + const Spells& GetSpells() const; + + void AddSkillAffector(const SkillAffector& affector); + std::vector& GetSkillAffectors(); + const std::vector& GetSkillAffectors() const; CharIndex mCharacterIndex; std::string mName; diff --git a/bak/coordinates.cpp b/bak/coordinates.cpp index b509d42..922bf35 100644 --- a/bak/coordinates.cpp +++ b/bak/coordinates.cpp @@ -84,6 +84,6 @@ std::ostream& operator<<(std::ostream& os, const Location& l) std::uint16_t HeadingToFullMapAngle(std::uint16_t heading) { constexpr auto unit = 0xff / 8; - return 4 * ((heading/ unit) % 8); + return 4 * ((heading / unit) % 8); } } diff --git a/bak/dialogSources.hpp b/bak/dialogSources.hpp index fd25c5e..a3e3651 100644 --- a/bak/dialogSources.hpp +++ b/bak/dialogSources.hpp @@ -1,6 +1,7 @@ #pragma once -#include "dialog.hpp" +#include "bak/types.hpp" +#include "bak/dialog.hpp" #include #include @@ -51,9 +52,9 @@ class DialogSources return KeyTarget{0x186a00 + index}; } - static KeyTarget GetChapterStartText(unsigned index) + static KeyTarget GetChapterStartText(Chapter chapter) { - return BAK::KeyTarget{mChapterFullMapScreenText + index}; + return BAK::KeyTarget{mChapterFullMapScreenText + (chapter.mValue - 1)}; } static Target GetChoiceResult(KeyTarget dialog, unsigned index) diff --git a/bak/gameState.cpp b/bak/gameState.cpp index ff31f7b..2c2cbda 100644 --- a/bak/gameState.cpp +++ b/bak/gameState.cpp @@ -1,5 +1,6 @@ #include "bak/gameState.hpp" +#include "bak/coordinates.hpp" #include "bak/state/customStateChoice.hpp" #include "bak/state/dialog.hpp" @@ -173,6 +174,12 @@ void GameState::SetLocation(GamePositionAndHeading posAndHeading) } } +void GameState::SetMapLocation(MapLocation location) const +{ + if (mGameData) + mGameData->mMapLocation = location; +} + GamePositionAndHeading GameState::GetLocation() const { if (mGameData) @@ -181,6 +188,14 @@ GamePositionAndHeading GameState::GetLocation() const return GamePositionAndHeading{glm::uvec2{10 * 64000, 15 * 64000}, 0 }; } +MapLocation GameState::GetMapLocation() const +{ + if (mGameData) + return mGameData->mMapLocation; + + return MapLocation{{20, 20}, 0}; +} + ZoneNumber GameState::GetZone() const { return mZone; diff --git a/bak/gameState.hpp b/bak/gameState.hpp index 25126af..8d58106 100644 --- a/bak/gameState.hpp +++ b/bak/gameState.hpp @@ -2,6 +2,7 @@ #include "bak/container.hpp" #include "bak/constants.hpp" +#include "bak/coordinates.hpp" #include "bak/dialog.hpp" #include "bak/dialogAction.hpp" #include "bak/dialogChoice.hpp" @@ -86,7 +87,9 @@ class GameState void SetLocation(Location loc); void SetLocation(GamePositionAndHeading posAndHeading); + void SetMapLocation(MapLocation) const; GamePositionAndHeading GetLocation() const; + MapLocation GetMapLocation() const; ZoneNumber GetZone() const; auto& GetTimeExpiringState() { return mTimeExpiringState; } @@ -104,6 +107,7 @@ class GameState std::optional GetActor(unsigned actor) const; bool GetSpellActive(StaticSpells spell) const; + bool ClearActiveSpells(); // prefer to use this function when getting best skill // as it will set the appropriate internal state. diff --git a/game/gameRunner.hpp b/game/gameRunner.hpp index ae4f244..be2543a 100644 --- a/game/gameRunner.hpp +++ b/game/gameRunner.hpp @@ -197,7 +197,6 @@ class GameRunner : public BAK::IZoneLoader } } - auto& containers = mGameState.GetContainers( BAK::ZoneNumber{mZoneData->mZoneLabel.GetZoneNumber()}); for (auto& container : containers) @@ -262,16 +261,16 @@ class GameRunner : public BAK::IZoneLoader { auto id = mSystems->GetNextItemId(); const auto dims = enc.GetDims(); - if (std::holds_alternative(enc.GetEncounter())) - { - mSystems->AddRenderable( - Renderable{ - id, - mZoneData->mObjects.GetObject(std::string{BAK::Encounter::ToString(enc.GetEncounter())}), - enc.GetLocation(), - glm::vec3{0.0}, - glm::vec3{dims.x, 50.0, dims.y} / BAK::gWorldScale}); - } + //if (std::holds_alternative(enc.GetEncounter())) + //{ + // mSystems->AddRenderable( + // Renderable{ + // id, + // mZoneData->mObjects.GetObject(std::string{BAK::Encounter::ToString(enc.GetEncounter())}), + // enc.GetLocation(), + // glm::vec3{0.0}, + // glm::vec3{dims.x, 50.0, dims.y} / BAK::gWorldScale}); + //} mSystems->AddIntersectable( Intersectable{ @@ -667,27 +666,27 @@ class GameRunner : public BAK::IZoneLoader std::visit( overloaded{ [&](const BAK::Encounter::GDSEntry& gds){ - if (mGuiManager.mScreenStack.size() == 1) + if (mGuiManager.InMainView()) DoGDSEncounter(encounter, gds); }, [&](const BAK::Encounter::Block& block){ - if (mGuiManager.mScreenStack.size() == 1) + if (mGuiManager.InMainView()) DoBlockEncounter(encounter, block); }, [&](const BAK::Encounter::Combat& combat){ - if (mGuiManager.mScreenStack.size() == 1) + if (mGuiManager.InMainView()) CheckAndDoCombatEncounter(encounter, combat); }, [&](const BAK::Encounter::Dialog& dialog){ - if (mGuiManager.mScreenStack.size() == 1) + if (mGuiManager.InMainView()) DoDialogEncounter(encounter, dialog); }, [&](const BAK::Encounter::EventFlag& flag){ - if (mGuiManager.mScreenStack.size() == 1) + if (mGuiManager.InMainView()) DoEventFlagEncounter(encounter, flag); }, [&](const BAK::Encounter::Zone& zone){ - if (mGuiManager.mScreenStack.size() == 1) + if (mGuiManager.InMainView()) DoZoneEncounter(encounter, zone); }, }, diff --git a/gui/IGuiManager.hpp b/gui/IGuiManager.hpp index b3acbc3..b6f0324 100644 --- a/gui/IGuiManager.hpp +++ b/gui/IGuiManager.hpp @@ -28,7 +28,7 @@ class IGuiManager virtual void ExitGDSScene() = 0; virtual void StartDialog(BAK::Target, bool tooltip, bool drawWorldFrame, IDialogScene*) = 0; - virtual void PlayCutscene(std::vector actions) = 0; + virtual void PlayCutscene(std::vector actions, std::function&&) = 0; virtual void ShowCharacterPortrait(BAK::ActiveCharIndex) = 0; virtual void ExitSimpleScreen() = 0; @@ -42,6 +42,7 @@ class IGuiManager virtual void ShowCamp(bool isInn, BAK::ShopStats* inn) = 0; virtual void ShowCast(bool inCombat) = 0; virtual void ShowFullMap() = 0; + virtual void ShowGameStartMap() = 0; virtual void ShowTeleport(unsigned sourceTemple) = 0; virtual void ShowCureScreen( unsigned templeNumber, diff --git a/gui/callbackDelay.hpp b/gui/callbackDelay.hpp new file mode 100644 index 0000000..94cbdb0 --- /dev/null +++ b/gui/callbackDelay.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "gui/IAnimator.hpp" + +#include + +namespace Gui { + +class CallbackDelay : public IAnimator +{ +public: + CallbackDelay( + std::function&& callback, + double delay) + : + mAlive{true}, + mDelay{delay}, + mCallback{std::move(callback)} + { + } + + void OnTimeDelta(double delta) override + { + mDelay -= delta; + if (mDelay <= 0) + { + mCallback(); + mAlive = false; + } + } + + bool IsAlive() const override + { + return mAlive; + } + + bool mAlive; + double mDelay; + std::function mCallback; +}; + +} diff --git a/gui/contents.hpp b/gui/contents.hpp index 6ae1175..6d19e0c 100644 --- a/gui/contents.hpp +++ b/gui/contents.hpp @@ -119,7 +119,7 @@ class ContentsScreen: public Widget auto start = BAK::CutsceneList::GetStartScene(chapter); auto finish = BAK::CutsceneList::GetFinishScene(chapter); start.insert(start.end(), finish.begin(), finish.end()); - mGuiManager.PlayCutscene(start); + mGuiManager.PlayCutscene(start, []{}); } IGuiManager& mGuiManager; diff --git a/gui/dynamicTTM.cpp b/gui/dynamicTTM.cpp index bd159be..b1168b1 100644 --- a/gui/dynamicTTM.cpp +++ b/gui/dynamicTTM.cpp @@ -1,53 +1,25 @@ #include "gui/dynamicTTM.hpp" +#include "gui/backgrounds.hpp" +#include "gui/colors.hpp" +#include "gui/callbackDelay.hpp" +#include "gui/fontManager.hpp" + #include "bak/dialogSources.hpp" #include "bak/imageStore.hpp" #include "bak/screen.hpp" #include "bak/textureFactory.hpp" - +#include "bak/ttmRenderer.hpp" #include "bak/dialog.hpp" + #include "com/assert.hpp" #include "com/logger.hpp" #include "graphics/types.hpp" -#include "gui/colors.hpp" namespace Gui { -class CallbackDelay : public IAnimator -{ -public: - CallbackDelay( - std::function&& callback, - double delay) - : - mAlive{true}, - mDelay{delay}, - mCallback{std::move(callback)} - { - } - - void OnTimeDelta(double delta) override - { - mDelay -= delta; - if (mDelay <= 0) - { - mCallback(); - mAlive = false; - } - } - - bool IsAlive() const override - { - return mAlive; - } - - bool mAlive; - double mDelay; - std::function mCallback; -}; - DynamicTTM::DynamicTTM( Graphics::SpriteManager& spriteManager, AnimatorStore& animatorStore, diff --git a/gui/dynamicTTM.hpp b/gui/dynamicTTM.hpp index 3d04a1a..53bc558 100644 --- a/gui/dynamicTTM.hpp +++ b/gui/dynamicTTM.hpp @@ -1,20 +1,23 @@ #pragma once -#include "bak/ttmRenderer.hpp" #include "bak/ttmRunner.hpp" #include "com/logger.hpp" -#include "graphics/guiTypes.hpp" #include "graphics/sprites.hpp" #include "gui/animatorStore.hpp" -#include "gui/scene.hpp" -#include "gui/dialogDisplay.hpp" #include "gui/core/widget.hpp" +#include "gui/button.hpp" +#include "gui/textBox.hpp" + +#include namespace Gui { +class Backgrounds; +class Font; + class DynamicTTM { diff --git a/gui/fullMap.hpp b/gui/fullMap.hpp index 280f151..6e382ec 100644 --- a/gui/fullMap.hpp +++ b/gui/fullMap.hpp @@ -3,22 +3,19 @@ #include "bak/coordinates.hpp" #include "bak/fmap.hpp" #include "bak/layout.hpp" -#include "bak/textureFactory.hpp" +#include "bak/dialogSources.hpp" -#include "gui/IDialogScene.hpp" #include "gui/IGuiManager.hpp" #include "gui/backgrounds.hpp" -#include "gui/icons.hpp" -#include "gui/colors.hpp" +#include "gui/callbackDelay.hpp" #include "gui/clickButton.hpp" +#include "gui/icons.hpp" +#include "gui/tickAnimator.hpp" #include "gui/townLabel.hpp" #include "gui/core/widget.hpp" #include - -#include -#include -#include +#include namespace Gui { @@ -49,7 +46,6 @@ class FullMap : public Widget mFont{font}, mGameState{gameState}, mIcons{icons}, - mDialogScene{}, mFMapXY{}, mFMapTowns{}, mLayout{sLayoutFile}, @@ -58,7 +54,23 @@ class FullMap : public Widget mLayout.GetWidgetDimensions(sExitWidget), mFont, "#Exit", - [this]{ mGuiManager.DoFade(.8, [this]{mGuiManager.ExitSimpleScreen(); }); } + [this]{ + mGuiManager.DoFade(.8, [this]{ + mPlayerPositionFlasher->Stop(); + mGuiManager.ExitSimpleScreen(); }); + } + }, + mPopup{ + glm::vec2{}, + glm::vec2{}, + Color::buttonBackground, + Color::buttonHighlight, + Color::buttonShadow, + Color::black + }, + mPopupText{ + glm::vec2{}, + glm::vec2{} }, mPlayerLocation{ ImageTag{}, @@ -69,6 +81,7 @@ class FullMap : public Widget false }, mTowns{}, + mGameStartScreenMode{false}, mLogger{Logging::LogState::GetLogger("Gui::FullMap")} { mTowns.reserve(mFMapTowns.GetTowns().size()); @@ -81,15 +94,32 @@ class FullMap : public Widget town.mName); } + mPopup.AddChildBack(&mPopupText); + } + + void DisplayMapMode() + { + mGameStartScreenMode = false; + StartPlayerPositionFlasher(); AddChildren(); } - void AddChildren() + void DisplayGameStartMode(BAK::Chapter chapter, BAK::MapLocation location) { - AddChildBack(&mExitButton); - AddChildBack(&mPlayerLocation); - for (auto& t : mTowns) - AddChildBack(&t); + mGameStartScreenMode = true; + SetPlayerLocation(location); + DisplayGameStart(chapter); + AddChildren(); + + StartPlayerPositionFlasher(); + + mGuiManager.AddAnimator( + std::make_unique( + [&](){ + mGuiManager.EnterMainView(); + mPlayerPositionFlasher->Stop(); + }, + 3)); } void UpdateLocation() @@ -97,14 +127,14 @@ class FullMap : public Widget SetPlayerLocation(mGameState.GetZone(), mGameState.GetLocation()); } +private: void SetPlayerLocation( BAK::ZoneNumber zone, BAK::GamePositionAndHeading location) { - const auto& [ss, ti, dims] = mIcons.GetFullMapIcon(BAK::HeadingToFullMapAngle(location.mHeading)); - mPlayerLocation.SetSpriteSheet(ss); - mPlayerLocation.SetTexture(ti); - mPlayerLocation.SetDimensions(dims); + mPlayerPositionBaseIcon = BAK::HeadingToFullMapAngle(location.mHeading); + + UpdatePlayerPositionIcon(); mPlayerLocation.SetCenter( mFMapXY.GetTileCoords( @@ -112,12 +142,77 @@ class FullMap : public Widget BAK::GetTile(location.mPosition))); } -private: + void SetPlayerLocation(BAK::MapLocation location) + { + mPlayerPositionBaseIcon = BAK::HeadingToFullMapAngle(location.mHeading); + mPlayerLocation.SetCenter(location.mPosition); + UpdatePlayerPositionIcon(); + } + + void DisplayGameStart(BAK::Chapter chapter) + { + const auto& snippet = BAK::DialogStore::Get().GetSnippet( + BAK::DialogSources::GetChapterStartText(chapter)); + assert(snippet.GetPopup()); + const auto popup = snippet.GetPopup(); + mLogger.Debug() << "Show snippet;" << snippet << "\n"; + mPopup.SetPosition(popup->mPos); + mPopup.SetDimensions(popup->mDims); + mPopupText.SetPosition(glm::vec2{1}); + mPopupText.SetDimensions(popup->mDims); + mPopupText.SetText(mFont, snippet.GetText(), true, true); + } + + void StartPlayerPositionFlasher() + { + auto flasher = std::make_unique( + .1, + [&](){ + if (mPlayerPositionIconOffset == 3) + { + mPlayerPositionIconPulseDirection = -1; + } + else if (mPlayerPositionIconOffset == 0) + { + mPlayerPositionIconPulseDirection = 1; + } + + mPlayerPositionIconOffset += mPlayerPositionIconPulseDirection; + UpdatePlayerPositionIcon(); + }); + mPlayerPositionFlasher = flasher.get(); + mGuiManager.AddAnimator(std::move(flasher)); + } + + void UpdatePlayerPositionIcon() + { + const auto& [ss, ti, dims] = mIcons.GetFullMapIcon(mPlayerPositionBaseIcon + mPlayerPositionIconOffset); + mPlayerLocation.SetSpriteSheet(ss); + mPlayerLocation.SetTexture(ti); + mPlayerLocation.SetDimensions(dims); + } + + void AddChildren() + { + ClearChildren(); + + AddChildBack(&mPlayerLocation); + if (mGameStartScreenMode) + { + AddChildBack(&mPopup); + } + else + { + AddChildBack(&mExitButton); + for (auto& t : mTowns) + AddChildBack(&t); + } + } + IGuiManager& mGuiManager; const Font& mFont; BAK::GameState& mGameState; const Icons& mIcons; - NullDialogScene mDialogScene; BAK::FMapXY mFMapXY; BAK::FMapTowns mFMapTowns; @@ -125,9 +220,17 @@ class FullMap : public Widget BAK::Layout mLayout; ClickButton mExitButton; + Button mPopup; + TextBox mPopupText; Widget mPlayerLocation; std::vector mTowns; + unsigned mPlayerPositionBaseIcon; + unsigned mPlayerPositionIconOffset; + int mPlayerPositionIconPulseDirection{1}; + TickAnimator* mPlayerPositionFlasher; + + bool mGameStartScreenMode; const Logging::Logger& mLogger; }; diff --git a/gui/guiManager.hpp b/gui/guiManager.hpp index 70d0628..3640a7e 100644 --- a/gui/guiManager.hpp +++ b/gui/guiManager.hpp @@ -7,6 +7,7 @@ #include "bak/gameState.hpp" #include "bak/saveManager.hpp" +#include "bak/startupFiles.hpp" #include "com/assert.hpp" #include "gui/IDialogScene.hpp" @@ -35,6 +36,7 @@ #include #include +#include #include namespace Gui { @@ -230,13 +232,15 @@ class GuiManager final : public Widget, public IGuiManager } void PlayCutscene( - std::vector actions) override + std::vector actions, + std::function&& cutsceneFinished) override { + mCutsceneFinished = std::move(cutsceneFinished); for (const auto& action : actions) { mCutscenePlayer.QueueAction(action); } - DoFade(1.0, [this]{ + DoFade(1.5, [this]{ mPreviousScreen = mScreenStack.Top(); mScreenStack.PopScreen(); mScreenStack.PushScreen(&mCutscenePlayer); @@ -246,10 +250,11 @@ class GuiManager final : public Widget, public IGuiManager void CutsceneFinished() { - DoFade(1.0, [this]{ - mScreenStack.PopScreen(); - mScreenStack.PushScreen(mPreviousScreen); - }); + //DoFade(1.0, [this]{ + // mScreenStack.PopScreen(); + // mScreenStack.PushScreen(mPreviousScreen); + //}); + mCutsceneFinished(); } bool InMainView() const override @@ -260,13 +265,6 @@ class GuiManager final : public Widget, public IGuiManager void EnterMainView() override { mMainView.UpdatePartyMembers(mGameState); - // There is a bug here. - // If we load a save where we are in a combat - // we will evaluate and possibly trigger the combat - // while fading. This means the pop screen will - // pop the combat entry dialog, not the main menu - // screen as we want. To fix this we need to add a - // "in main view" state to the app... DoFade(1.0, [this]{ mScreenStack.PopScreen(); mScreenStack.PushScreen(&mMainView); @@ -400,6 +398,7 @@ class GuiManager final : public Widget, public IGuiManager { DoTeleport(*teleport); } + mMainView.UpdatePartyMembers(mGameState); } void DoTeleport(BAK::TeleportIndex teleport) override @@ -540,7 +539,17 @@ class GuiManager final : public Widget, public IGuiManager void ShowFullMap() override { - DoFade(.8, [this]{ + mFullMap.DisplayMapMode(); + DoFade(1.0, [this]{ + mScreenStack.PushScreen(&mFullMap); + }); + } + + void ShowGameStartMap() override + { + DoFade(1.0, [this]{ + mScreenStack.PopScreen(); + mFullMap.DisplayGameStartMode(mGameState.GetChapter(), mGameState.GetMapLocation()); mScreenStack.PushScreen(&mFullMap); }); } @@ -596,7 +605,7 @@ class GuiManager final : public Widget, public IGuiManager guiScreen.mFinished(); } -//private: +private: void FadeInDone() { ASSERT(mFadeFunction); @@ -622,7 +631,9 @@ class GuiManager final : public Widget, public IGuiManager Graphics::SpriteManager& mSpriteManager; CutscenePlayer mCutscenePlayer; +public: MainView mMainView; +private: MainMenuScreen mMainMenu; InfoScreen mInfoScreen; InventoryScreen mInventoryScreen; @@ -630,11 +641,14 @@ class GuiManager final : public Widget, public IGuiManager Cast::CastScreen mCastScreen; CureScreen mCureScreen; LockScreen mLockScreen; +public: FullMap mFullMap; +private: MoredhelScreen mMoredhelScreen; TeleportScreen mTeleportScreen; FadeScreen mFadeScreen; std::function mFadeFunction; + std::function mCutsceneFinished; std::vector> mGdsScenes; IDialogScene* mDialogScene; diff --git a/gui/mainMenuScreen.hpp b/gui/mainMenuScreen.hpp index d48fcc6..d412bda 100644 --- a/gui/mainMenuScreen.hpp +++ b/gui/mainMenuScreen.hpp @@ -137,10 +137,7 @@ class MainMenuScreen: public Widget font, [this]{ BackToMainMenu(); }, [this](const auto& file){ - mState = State::MainMenu; - AudioA::AudioManager::Get().PopTrack(); - mGuiManager.LoadGame(file, std::nullopt); - EnterMainView(); + Load(file); }, [this](const auto& saveFile){ mState = State::MainMenu; @@ -219,8 +216,19 @@ class MainMenuScreen: public Widget void StartNewGame() { AudioA::AudioManager::Get().PopTrack(); + auto start = BAK::CutsceneList::GetStartScene(BAK::Chapter{1}); + mGuiManager.PlayCutscene(start , [&]{ + mGuiManager.ShowGameStartMap(); + }); mGuiManager.LoadGame("startup.gam", std::make_optional(BAK::Chapter{1})); - mGuiManager.EnterMainView(); + } + + void Load(std::string file) + { + mState = State::MainMenu; + AudioA::AudioManager::Get().PopTrack(); + mGuiManager.LoadGame(file, std::nullopt); + mGuiManager.ShowGameStartMap(); } void AddChildren()