diff --git a/.gitmodules b/.gitmodules index d0c91b5a..8fd7574a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,6 @@ [submodule "lib/libdmusic"] path = lib/libdmusic url = https://github.com/frabert/libdmusic.git +[submodule "lib/optional"] + path = lib/optional + url = https://github.com/TartanLlama/optional.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 43ece235..3a991b87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -313,6 +313,7 @@ else() endif() add_subdirectory(lib/adpcm) +include_directories(lib/optional) add_subdirectory(src/target) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT REGoth) diff --git a/lib/optional b/lib/optional new file mode 160000 index 00000000..effc9411 --- /dev/null +++ b/lib/optional @@ -0,0 +1 @@ +Subproject commit effc9411169fc983a4647b57f5669c68b2893421 diff --git a/src/engine/WorldMesh.cpp b/src/engine/WorldMesh.cpp index 477adb11..6ecdbe59 100644 --- a/src/engine/WorldMesh.cpp +++ b/src/engine/WorldMesh.cpp @@ -52,3 +52,12 @@ ZenLoad::zCMaterialData WorldMesh::getMatData(size_t triangleIdx) const assert(m_WorldMeshData.triangles[triangleIdx].submeshIndex < m_WorldMeshData.subMeshes.size()); return m_WorldMeshData.subMeshes[m_WorldMeshData.triangles[triangleIdx].submeshIndex].material; } + +ZenLoad::MaterialGroup WorldMesh::getMaterialGroupOfTriangle(uint32_t triangleIdx) +{ + Math::float3 v3[3]; + uint8_t matgroup; + // Beware! If triangle index is given such that the triangle is a building triangle of a VOB, this function will return material of the underlying worldmesh!!! + getTriangle(triangleIdx, v3, matgroup); + return static_cast(matgroup); +} diff --git a/src/engine/WorldMesh.h b/src/engine/WorldMesh.h index 1ebccc51..9278cc8c 100644 --- a/src/engine/WorldMesh.h +++ b/src/engine/WorldMesh.h @@ -38,6 +38,11 @@ namespace World */ void getTriangle(size_t triangleIdx, Math::float3* v3, uint8_t& matgroup); + /** + * Returns material group of give triangle index + */ + ZenLoad::MaterialGroup getMaterialGroupOfTriangle(uint32_t triangleIdx); + /** * @return Boundingbox max/min */ diff --git a/src/logic/CharacterEquipment.cpp b/src/logic/CharacterEquipment.cpp new file mode 100644 index 00000000..c5b95385 --- /dev/null +++ b/src/logic/CharacterEquipment.cpp @@ -0,0 +1,680 @@ +// +// Created by andre on 28.06.18. +// + +#include "CharacterEquipment.h" +#include +#include +#include + +using namespace Logic; + +CharacterEquipment::CharacterEquipment(World::WorldInstance& world, Handle::EntityHandle characterEntity) + : m_CharacterEntity(characterEntity) + , m_World(world) +{ +} + +bool CharacterEquipment::equipItemToSlot(ItemHandle item, Slot slot) +{ + if (!isItemEquipable(item)) + return false; + + if (!isItemTypeCorrectForSlot(item, slot)) + return false; + + LogInfo() << "Equipping item: " << getInstanceNameOfItem(item); + + switch (getKindOfItem(item)) + { + case Kind::MELEE: + return equipMelee(item); + + case Kind::BOW: + return equipBow(item); + + case Kind::AMULET: + return equipAmulet(item); + + case Kind::RING: + return equipRing(item, slot); + + case Kind::MAGIC: + return equipMagic(item, slot); + + case Kind::BELT: + return equipBelt(item); + + case Kind::ARMOR: + return equipArmor(item); + + case Kind::OTHER: + default: + return false; + } + + return true; +} + +bool CharacterEquipment::equipItem(ItemHandle item) +{ + if (!isItemEquipable(item)) + return false; + + tl::optional slot; + switch (getKindOfItem(item)) + { + case Kind::MELEE: + case Kind::BOW: + case Kind::AMULET: + case Kind::BELT: + case Kind::ARMOR: + slot = getCorrectSlotForItem(item); + break; + + case Kind::RING: + slot = findAnyFreeRingSlot(); + break; + + case Kind::MAGIC: + slot = findAnyFreeMagicSlot(); + break; + + case Kind::OTHER: + default: + return false; + } + + if (!slot) + return false; + + return equipItemToSlot(item, *slot); +} + +void CharacterEquipment::unequipItemInSlot(Slot slot) +{ + switch (slot) + { + case Slot::MELEE: + removeCharacterModelAttachment(EModelNode::Longsword); + removeCharacterModelAttachment(EModelNode::Sword); + break; + + case Slot::BOW: + removeCharacterModelAttachment(EModelNode::Bow); + removeCharacterModelAttachment(EModelNode::Crossbow); + break; + + case Slot::ARMOR: + switchToDefaultCharacterModel(); + break; + + default: // Everything else can be taken off without visual changes + break; + } +} + +void CharacterEquipment::unequipItem(ItemHandle item) +{ + auto slot = findSlotItemWasEquippedTo(item); + + if (slot) + { + unequipItemInSlot(*slot); + } +} + +bool CharacterEquipment::equipMelee(ItemHandle item) +{ + setItemInSlot(item, Slot::MELEE); + + return true; +} + +bool CharacterEquipment::equipBow(ItemHandle item) +{ + setItemInSlot(item, Slot::BOW); + + return true; +} + +bool CharacterEquipment::equipAmulet(ItemHandle item) +{ + setItemInSlot(item, Slot::AMULET); + return true; +} + +bool CharacterEquipment::equipRing(ItemHandle item, Slot slot) +{ + setItemInSlot(item, slot); + return true; +} + +bool CharacterEquipment::equipMagic(ItemHandle item, Slot slot) +{ + setItemInSlot(item, slot); + return true; +} + +bool CharacterEquipment::equipBelt(ItemHandle item) +{ + setItemInSlot(item, Slot::BELT); + return true; +} + +bool CharacterEquipment::equipArmor(ItemHandle item) +{ + auto data = getDataOfItem(item); + + if (!data) + return false; + + switchCharacterModelArmor(data->visual_change); + setItemInSlot(item, Slot::ARMOR); + return true; +} + +void CharacterEquipment::showMeleeWeaponOnCharacter() +{ + using Daedalus::GEngineClasses::C_Item; + + auto item = getItemInSlot(Slot::MELEE); + auto data = getDataOfItem(item); + + if (!data) + return; + + EModelNode node; + if (data->flags & C_Item::Flags::ITEM_2HD_AXE) + { + node = EModelNode::Longsword; + } + else if (data->flags & C_Item::Flags::ITEM_2HD_SWD) + { + node = EModelNode::Longsword; + } + else if (data->flags & C_Item::Flags::ITEM_AXE) + { + node = EModelNode::Sword; + } + else if (data->flags & C_Item::Flags::ITEM_SWD) + { + node = EModelNode::Sword; + } + else + { + // What is this? + return; + } + + setCharacterModelAttachment(getItemVisual(item), node); +} + +void CharacterEquipment::showBowWeaponOnCharacter() +{ + using Daedalus::GEngineClasses::C_Item; + auto item = getItemInSlot(Slot::BOW); + auto data = getDataOfItem(item); + + if (!data) + return; + + if (data->flags & C_Item::Flags::ITEM_CROSSBOW) + { + setCharacterModelAttachment(getItemVisual(item), EModelNode::Crossbow); + } + else if (data->flags & C_Item::Flags::ITEM_BOW) + { + setCharacterModelAttachment(getItemVisual(item), EModelNode::Bow); + } + else + { + // What is this? + } +} + +void CharacterEquipment::putMeleeWeaponInCharactersHand() +{ + auto item = getItemInSlot(Slot::MELEE); + + putItemIntoRightHand(item); + removeCharacterModelAttachment(EModelNode::Sword); + removeCharacterModelAttachment(EModelNode::Longsword); +} + +void CharacterEquipment::putBowWeaponInCharactersHand() +{ + auto item = getItemInSlot(Slot::BOW); + + putItemIntoRightHand(item); + removeCharacterModelAttachment(EModelNode::Bow); + removeCharacterModelAttachment(EModelNode::Crossbow); +} + +void CharacterEquipment::removeItemInCharactersHandAndShowWeaponsOnBody() +{ + removeCharacterModelAttachment(EModelNode::Lefthand); + removeCharacterModelAttachment(EModelNode::Righthand); + + // Need to put those back onto the character if they exist + showBowWeaponOnCharacter(); + showMeleeWeaponOnCharacter(); +} + +tl::optional CharacterEquipment::getCorrectSlotForItem(ItemHandle item) const +{ + switch (getKindOfItem(item)) + { + case Kind::MELEE: + return Slot::MELEE; + case Kind::BOW: + return Slot::BOW; + case Kind::AMULET: + return Slot::AMULET; + case Kind::RING: + return Slot::RING_LEFT; + case Kind::MAGIC: + return Slot::MAGIC_0; + case Kind::ARMOR: + return Slot::ARMOR; + case Kind::BELT: + return Slot::BELT; + case Kind::OTHER: + default: + return tl::nullopt; + } +} + +CharacterEquipment::Kind CharacterEquipment::getKindOfItem(ItemHandle item) const +{ + using Daedalus::GEngineClasses::C_Item; + auto itemData = getDataOfItem(item); + + if (!itemData) + return Kind::OTHER; + + // Only equippable items get a kind + if ((itemData->mainflag & C_Item::ITM_CAT_EQUIPABLE) == 0) + return Kind::OTHER; + + // Magic and armor can only be identified through the mainflags + if ((itemData->mainflag & C_Item::ITM_CAT_RUNE) != 0) + return Kind::MAGIC; + + if ((itemData->mainflag & C_Item::ITM_CAT_MAGIC) != 0) + return Kind::MAGIC; + + if ((itemData->mainflag & C_Item::ITM_CAT_ARMOR) != 0) + return Kind::ARMOR; + + std::pair pairs[] = { + {C_Item::Flags::ITEM_DAG, Kind::MELEE}, + {C_Item::Flags::ITEM_SWD, Kind::MELEE}, + {C_Item::Flags::ITEM_AXE, Kind::MELEE}, + {C_Item::Flags::ITEM_2HD_SWD, Kind::MELEE}, + {C_Item::Flags::ITEM_2HD_AXE, Kind::MELEE}, + {C_Item::Flags::ITEM_BOW, Kind::BOW}, + {C_Item::Flags::ITEM_CROSSBOW, Kind::BOW}, + {C_Item::Flags::ITEM_AMULET, Kind::AMULET}, + {C_Item::Flags::ITEM_RING, Kind::RING}, + {C_Item::Flags::ITEM_BELT, Kind::BELT}, + {C_Item::Flags::ITEM_MISSION, Kind::OTHER}, + }; + + for (auto& p : pairs) + { + if (itemData->flags & p.first) + return p.second; + } + + return Kind::OTHER; +} + +CharacterEquipment::WeaponKind CharacterEquipment::getWeaponKindOfItem(ItemHandle item) const +{ + using Daedalus::GEngineClasses::C_Item; + + auto itemData = getDataOfItem(item); + + if (!itemData) + return WeaponKind::NONE; + + std::pair pairs[] = { + {C_Item::Flags::ITEM_DAG, WeaponKind::MELEE_1H}, + {C_Item::Flags::ITEM_SWD, WeaponKind::MELEE_1H}, + {C_Item::Flags::ITEM_AXE, WeaponKind::MELEE_1H}, + {C_Item::Flags::ITEM_2HD_SWD, WeaponKind::MELEE_2H}, + {C_Item::Flags::ITEM_2HD_AXE, WeaponKind::MELEE_2H}, + {C_Item::Flags::ITEM_BOW, WeaponKind::BOW}, + {C_Item::Flags::ITEM_CROSSBOW, WeaponKind::CROSSBOW}, + }; + + for (auto& p : pairs) + { + if (itemData->flags & p.first) + return p.second; + } + + return WeaponKind::NONE; +} + +tl::optional CharacterEquipment::findAnyFreeMagicSlot() const +{ + Slot possibleSlots[] = { + Slot::MAGIC_0, + Slot::MAGIC_1, + Slot::MAGIC_2, + Slot::MAGIC_3, + Slot::MAGIC_4, + Slot::MAGIC_5, + Slot::MAGIC_6, + Slot::MAGIC_7, + Slot::MAGIC_8, + Slot::MAGIC_9, + }; + + for (Slot s : possibleSlots) + { + if (!getItemInSlot(s).isValid()) + return s; + } + + return tl::nullopt; +} + +tl::optional CharacterEquipment::findAnyFreeRingSlot() const +{ + Slot possibleSlots[] = { + Slot::RING_LEFT, + Slot::RING_RIGHT, + }; + + for (Slot s : possibleSlots) + { + if (!getItemInSlot(s).isValid()) + return s; + } + + return tl::nullopt; +} + +tl::optional CharacterEquipment::findSlotItemWasEquippedTo(ItemHandle item) const +{ + for (size_t i = 0; i < (size_t)Slot::COUNT; i++) + { + if (m_ItemsBySlot[i] == item) + return (Slot)i; + } + + return tl::nullopt; +} + +bool CharacterEquipment::hasMeleeWeaponEquipped() const +{ + return getItemInSlot(Slot::MELEE).isValid(); +} + +bool CharacterEquipment::hasBowEquipped() const +{ + return getItemInSlot(Slot::BOW).isValid(); +} + +bool CharacterEquipment::hasItemEquipped(ItemHandle item) const +{ + for (auto h : m_ItemsBySlot) + { + if (h == item) + return true; + } + + return false; +} + +bool CharacterEquipment::isItemTypeCorrectForSlot(ItemHandle item, Slot slot) const +{ + Kind kind = getKindOfItem(item); + + switch (kind) + { + case Kind::MELEE: + return slot == Slot::MELEE; + + case Kind::BOW: + return slot == Slot::BOW; + + case Kind::AMULET: + return slot == Slot::AMULET; + + case Kind::RING: + { + if (slot == Slot::RING_LEFT) + return true; + + if (slot == Slot::RING_RIGHT) + return true; + + return false; + } + + case Kind::MAGIC: + { + Slot possibleSlots[] = { + Slot::MAGIC_0, + Slot::MAGIC_1, + Slot::MAGIC_2, + Slot::MAGIC_3, + Slot::MAGIC_4, + Slot::MAGIC_5, + Slot::MAGIC_6, + Slot::MAGIC_7, + Slot::MAGIC_8, + Slot::MAGIC_9}; + + for (Slot s : possibleSlots) + { + if (slot == s) + return true; + } + + return false; + } + + case Kind::BELT: + return slot == Slot::BELT; + + case Kind::ARMOR: + return slot == Slot::ARMOR; + + case Kind::OTHER: + default: + return false; + } +} + +bool CharacterEquipment::isItemOfKind(ItemHandle item, Kind kind) const +{ + return getKindOfItem(item) == kind; +} + +bool CharacterEquipment::isItemEquipable(ItemHandle item) const +{ + return !isItemOfKind(item, Kind::OTHER); // All but OTHER can be equipped +} + +bool CharacterEquipment::doAttributesAllowUse(ItemHandle item) const +{ + using Daedalus::GEngineClasses::C_Item; + using Daedalus::GEngineClasses::C_Npc; + + C_Item& data = m_World.getScriptEngine().getGameState().getItem(item); + C_Npc& npc = getController().getScriptInstance(); + ScriptEngine& s = m_World.getScriptEngine(); + + for (size_t i = 0; i < Daedalus::GEngineClasses::C_Item::COND_ATR_MAX; i++) + { + // Why is 0 not allowed? That's how gothic is doing it though, as it seems... + if (data.cond_atr[i] > 0) + { + assert(data.cond_atr[i] < Daedalus::GEngineClasses::C_Npc::EATR_MAX); + + // Check for enough strength, etc. + if (npc.attribute[data.cond_atr[i]] < data.cond_value[i]) + { + return false; + } + } + } + + return true; +} + +tl::optional CharacterEquipment::getItemDataInSlot(Slot slot) const +{ + return getDataOfItem(getItemInSlot(slot)); +} + +std::string CharacterEquipment::getItemVisual(ItemHandle item) const +{ + auto data = getDataOfItem(item); + + if (!data) + return ""; + + return data->visual; +} + +CharacterEquipment::ItemHandle CharacterEquipment::getItemInSlot(Slot slot) const +{ + return m_ItemsBySlot[(size_t)slot]; +} + +void CharacterEquipment::setItemInSlot(ItemHandle item, Slot slot) +{ + m_ItemsBySlot[(size_t)slot] = item; +} + +PlayerController& CharacterEquipment::getController() const +{ + auto logic = m_World.getEntity(m_CharacterEntity).m_pLogicController; + assert(logic != nullptr); + return *reinterpret_cast(logic); +} + +tl::optional CharacterEquipment::getDataOfItem(ItemHandle item) const +{ + if (!item.isValid()) + return tl::nullopt; + + return m_World.getScriptEngine().getGameState().getItem(item); +} + +void CharacterEquipment::setCharacterModelAttachment(const std::string& visual, EModelNode node) +{ + ModelVisual* pVisual = getController().getModelVisual(); + + if (!pVisual) + return; + + pVisual->setNodeVisual(visual, node); +} + +void CharacterEquipment::setCharacterModelAttachment(ItemHandle item, EModelNode node) +{ + setCharacterModelAttachment(getItemVisual(item), node); +} + +void CharacterEquipment::removeCharacterModelAttachment(EModelNode node) +{ + setCharacterModelAttachment("", node); +} + +void CharacterEquipment::switchToDefaultCharacterModel() +{ + ModelVisual* pVisual = getController().getModelVisual(); + + if (!pVisual) + return; + + // TODO: Implement this. Actually not easy to get the default character model as it seems + // ModelVisual::BodyState state = pVisual->getBodyState(); + // state.bodyVisual = getController().getScriptInstance(). +} + +void CharacterEquipment::switchCharacterModelArmor(const std::string& visual) +{ + ModelVisual* pVisual = getController().getModelVisual(); + + if (!pVisual) + return; + + ModelVisual::BodyState state = pVisual->getBodyState(); + state.bodyVisual = visual; + + pVisual->setBodyState(state); +} + +void CharacterEquipment::putItemIntoRightHand(ItemHandle item) +{ + setCharacterModelAttachment(item, EModelNode::Righthand); +} + +void CharacterEquipment::putItemIntoLeftHand(ItemHandle item) +{ + setCharacterModelAttachment(item, EModelNode::Lefthand); +} + +void CharacterEquipment::exportSlots(json& j) const +{ + j["MELEE"] = getInstanceNameOfItem(getItemInSlot(Slot::MELEE)); + j["BOW"] = getInstanceNameOfItem(getItemInSlot(Slot::BOW)); + j["MAGIC_0"] = getInstanceNameOfItem(getItemInSlot(Slot::MAGIC_0)); + j["MAGIC_1"] = getInstanceNameOfItem(getItemInSlot(Slot::MAGIC_1)); + j["MAGIC_2"] = getInstanceNameOfItem(getItemInSlot(Slot::MAGIC_2)); + j["MAGIC_3"] = getInstanceNameOfItem(getItemInSlot(Slot::MAGIC_3)); + j["MAGIC_4"] = getInstanceNameOfItem(getItemInSlot(Slot::MAGIC_4)); + j["MAGIC_5"] = getInstanceNameOfItem(getItemInSlot(Slot::MAGIC_5)); + j["MAGIC_6"] = getInstanceNameOfItem(getItemInSlot(Slot::MAGIC_6)); + j["MAGIC_7"] = getInstanceNameOfItem(getItemInSlot(Slot::MAGIC_7)); + j["MAGIC_8"] = getInstanceNameOfItem(getItemInSlot(Slot::MAGIC_8)); + j["MAGIC_9"] = getInstanceNameOfItem(getItemInSlot(Slot::MAGIC_9)); + j["RING_LEFT"] = getInstanceNameOfItem(getItemInSlot(Slot::RING_LEFT)); + j["RING_RIGHT"] = getInstanceNameOfItem(getItemInSlot(Slot::RING_RIGHT)); + j["AMULET"] = getInstanceNameOfItem(getItemInSlot(Slot::AMULET)); + j["BELT"] = getInstanceNameOfItem(getItemInSlot(Slot::BELT)); + j["ARMOR"] = getInstanceNameOfItem(getItemInSlot(Slot::ARMOR)); +} + +void CharacterEquipment::importSlots(const json& j) +{ + auto& inventory = getController().getInventory(); + + equipItemToSlot(inventory.getItem(j["MELEE"].get()), Slot::MELEE); + equipItemToSlot(inventory.getItem(j["BOW"].get()), Slot::BOW); + equipItemToSlot(inventory.getItem(j["MAGIC_0"].get()), Slot::MAGIC_0); + equipItemToSlot(inventory.getItem(j["MAGIC_1"].get()), Slot::MAGIC_1); + equipItemToSlot(inventory.getItem(j["MAGIC_2"].get()), Slot::MAGIC_2); + equipItemToSlot(inventory.getItem(j["MAGIC_3"].get()), Slot::MAGIC_3); + equipItemToSlot(inventory.getItem(j["MAGIC_4"].get()), Slot::MAGIC_4); + equipItemToSlot(inventory.getItem(j["MAGIC_5"].get()), Slot::MAGIC_5); + equipItemToSlot(inventory.getItem(j["MAGIC_6"].get()), Slot::MAGIC_6); + equipItemToSlot(inventory.getItem(j["MAGIC_7"].get()), Slot::MAGIC_7); + equipItemToSlot(inventory.getItem(j["MAGIC_8"].get()), Slot::MAGIC_8); + equipItemToSlot(inventory.getItem(j["MAGIC_9"].get()), Slot::MAGIC_9); + equipItemToSlot(inventory.getItem(j["RING_LEFT"].get()), Slot::RING_LEFT); + equipItemToSlot(inventory.getItem(j["RING_RIGHT"].get()), Slot::RING_RIGHT); + equipItemToSlot(inventory.getItem(j["AMULET"].get()), Slot::AMULET); + equipItemToSlot(inventory.getItem(j["BELT"].get()), Slot::BELT); + equipItemToSlot(inventory.getItem(j["ARMOR"].get()), Slot::ARMOR); +} + +std::string CharacterEquipment::getInstanceNameOfItem(ItemHandle item) const +{ + auto data = getDataOfItem(item); + + if (!data) + return ""; + + ScriptEngine& s = m_World.getScriptEngine(); + + return s.getSymbolNameByIndex(data->instanceSymbol); +} diff --git a/src/logic/CharacterEquipment.h b/src/logic/CharacterEquipment.h new file mode 100644 index 00000000..d9a60d07 --- /dev/null +++ b/src/logic/CharacterEquipment.h @@ -0,0 +1,267 @@ +// +// Created by andre on 28.06.18. +// + +#pragma once + +#include +#include +#include +#include +#include + +namespace World +{ + class WorldInstance; +} + +namespace Logic +{ + class PlayerController; + /** + * Contains information about what a character currently has equipped. + * This encapsulates logic like whether the characters attributes are high enough + * and how many rings can be worn at once. + */ + class CharacterEquipment + { + public: + using ItemHandle = Daedalus::GameState::ItemHandle; + using ItemData = Daedalus::GEngineClasses::C_Item; + + enum class Slot : size_t + { + MELEE, + BOW, // + Crossbow + MAGIC_0, + MAGIC_1, + MAGIC_2, + MAGIC_3, + MAGIC_4, + MAGIC_5, + MAGIC_6, + MAGIC_7, + MAGIC_8, + MAGIC_9, + RING_LEFT, + RING_RIGHT, + AMULET, + BELT, + ARMOR, + COUNT, + }; + + enum class Kind : size_t + { + MELEE, + BOW, + AMULET, + RING, + MAGIC, + BELT, + ARMOR, + OTHER, + }; + + enum class WeaponKind + { + MELEE_1H, + MELEE_2H, + BOW, + CROSSBOW, + NONE, + }; + + CharacterEquipment(World::WorldInstance& world, Handle::EntityHandle characterEntity); + + /** + * Tries to equip the given item to the given slot. + * Returns true on success. + * Returns false if not possible (attributes, wrong kind of slot). + */ + bool equipItemToSlot(ItemHandle item, Slot slot); + + /** + * Equips the item to any free slot avilable for it. + * @return True, if a slot has been found and the item could be equipped + * False, if all possible slots are full or the attributes aren't correct + */ + bool equipItem(ItemHandle item); + + /** + * Takes of the item currently in the given slot + */ + void unequipItemInSlot(Slot slot); + + /** + * Unequips the given item, if it is currently equipped + */ + void unequipItem(ItemHandle item); + + /** + * Finds a slot the given item could potentially be equipped to. + * For magic and rings the first slot is returned, regardless of + * whether it's empty or not. + * This function also does not check whether the attributes of the + * underlaying character allow equipment. + */ + tl::optional getCorrectSlotForItem(ItemHandle item) const; + + /** + * Figures out which kind of item the given handle represents. + * Every kind except "OTHER" can be equipped. + */ + Kind getKindOfItem(ItemHandle item) const; + + /** + * @return More specific weapon type of the given item, ie. whether a weapon is 1 or 2 handed. + * If 'item' is not a weapon, it returns 'NONE'. + */ + WeaponKind getWeaponKindOfItem(ItemHandle item) const; + + /** + * Some types of items can be equipped to multiple slots. These functions + * return the first free one they can find. + */ + tl::optional findAnyFreeMagicSlot() const; + tl::optional findAnyFreeRingSlot() const; + + /** + * Finds the slot the given item was equipped to + */ + tl::optional findSlotItemWasEquippedTo(ItemHandle item) const; + + /** + * Status queries + */ + bool hasMeleeWeaponEquipped() const; + bool hasBowEquipped() const; + bool hasItemEquipped(ItemHandle item) const; + + /** + * Whether the given item could be equipped into the given slot. + * Ie. A ring cannot be equipped to the slot meant for melee-weapons. + */ + bool isItemTypeCorrectForSlot(ItemHandle item, Slot slot) const; + + /** + * @return Whether the given item is of the given kind + */ + bool isItemOfKind(ItemHandle item, Kind kind) const; + + /** + * @return Whether this item is possible equippable (not checking attributes) + */ + bool isItemEquipable(ItemHandle item) const; + + /** + * Whether the underlaying characters attributes are high enough to + * equip or use the given item. + * + * This can also be called on non-equipable items such as food where it makes sense! + */ + bool doAttributesAllowUse(ItemHandle item) const; + + /** + * @return the item currently equipped in the slot + */ + tl::optional getItemDataInSlot(Slot slot) const; + ItemHandle getItemInSlot(Slot slot) const; + + /** + * Shows the visual of the given item in the players hand + */ + void putItemIntoRightHand(ItemHandle item); + void putItemIntoLeftHand(ItemHandle item); + + /** + * Removes the weapons from the characters body and shows them + * inside their hand + */ + void putMeleeWeaponInCharactersHand(); + void putBowWeaponInCharactersHand(); + void removeItemInCharactersHandAndShowWeaponsOnBody(); + + + /** + * Savegame serialization + */ + + void exportSlots(json& j) const; + void importSlots(const json& j); + protected: + + /** + * Assigns the given item to the given slot without any checks + * or anything else + */ + void setItemInSlot(ItemHandle item, Slot slot); + + std::array m_ItemsBySlot; + + /** + * These functions perform the actual equipment. + * For these functions to succeed, the target slot needs to be + * empty! + * + * @return True, if equipping was possible + * False otherwise (ie. attributes too low) + */ + bool equipMelee(ItemHandle item); + bool equipBow(ItemHandle item); + bool equipAmulet(ItemHandle item); + bool equipRing(ItemHandle item, Slot slot); + bool equipMagic(ItemHandle item, Slot slot); + bool equipBelt(ItemHandle item); + bool equipArmor(ItemHandle item); + + /** + * Shows the attachment visuals on the character model for the + * currently equiped items + */ + void showMeleeWeaponOnCharacter(); + void showBowWeaponOnCharacter(); + + /** + * Sets the visual attachment on the character model + */ + void setCharacterModelAttachment(const std::string& visual, EModelNode node); + void setCharacterModelAttachment(ItemHandle item, EModelNode node); + void removeCharacterModelAttachment(EModelNode node); + + /** + * Switches to the default no-armor body mesh + */ + void switchToDefaultCharacterModel(); + + /** + * Switches to the given armor-visual + */ + void switchCharacterModelArmor(const std::string& visual); + + /** + * @return handle to the underlaying character + */ + PlayerController& getController() const; + + /** + * @return Data of the given item + */ + tl::optional getDataOfItem(ItemHandle item) const; + + + /** + * @return The visual for this item set by the scripts + */ + std::string getItemVisual(ItemHandle item) const; + + /** + * @return The script instance name of the given item + */ + std::string getInstanceNameOfItem(ItemHandle item) const; + + World::WorldInstance& m_World; + Handle::EntityHandle m_CharacterEntity; + }; + +} // namespace Logic diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index bdf27586..75adfb49 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -18,21 +18,19 @@ #include #include #include +#include #include +#include +#include #include +#include +#include +#include #include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include - #define DEBUG_PLAYER (isPlayerControlled() && false) @@ -60,12 +58,12 @@ namespace BodyNodes const char* NPC_NODE_HELMET = "ZS_HELMET"; const char* NPC_NODE_JAWS = "ZS_JAWS"; const char* NPC_NODE_TORSO = "ZS_TORSO"; -} +} // namespace BodyNodes /** * Default soundrange for SFX which doesn't specify a range. Gothic uses a default value of 35 meters. */ -static const float DEFAULT_CHARACTER_SOUND_RANGE = 35; // Meters +static const float DEFAULT_CHARACTER_SOUND_RANGE = 35; // Meters #define SINGLE_ACTION_KEY(key, fn) \ { \ @@ -88,10 +86,8 @@ PlayerController::PlayerController(World::WorldInstance& world, , m_NPCAnimationHandler(world, entity) , m_AIHandler(world, entity) , m_PathFinder(world) + , m_CharacterEquipment(world, entity) { - m_AIState.closestWaypoint = 0; - m_MoveState.currentPathPerc = 0; - m_NPCProperties.moveSpeed = 7.0f; m_NPCProperties.enablePhysics = true; m_MoveState.direction = Math::float3(1, 0, 0); @@ -100,8 +96,6 @@ PlayerController::PlayerController(World::WorldInstance& world, m_MoveState.ground.triangleIndex = 0; m_MoveState.ground.waterDepth = 0; m_MoveState.ground.trianglePosition = Math::float3(0, 0, 0); - m_AIState.targetWaypoint = World::Waynet::INVALID_WAYPOINT; - m_AIState.closestWaypoint = World::Waynet::INVALID_WAYPOINT; m_ScriptState.npcHandle = scriptInstance; @@ -110,15 +104,6 @@ PlayerController::PlayerController(World::WorldInstance& world, m_RefuseTalkTime = 0; - m_isDrawWeaponMelee = false; - m_isForward = false; - m_isBackward = false; - m_isTurnLeft = false; - m_isTurnRight = false; - m_isStrafeLeft = false; - m_isStrafeRight = false; - m_isSwimming = false; - m_LastAniRootPosUpdatedAniHash = 0; m_NoAniRootPosHack = false; @@ -140,17 +125,6 @@ void PlayerController::onUpdate(float deltaTime) ModelVisual* model = getModelVisual(); - // Build the route to follow this entity - if (m_RoutineState.entityTarget.isValid()) - { - Math::float3 targetPos = m_World.getEntity(m_RoutineState.entityTarget) - .m_WorldMatrix.Translation(); - - // FIXME: Doing this every frame is too often - size_t targetWP = World::Waynet::findNearestWaypointTo(m_World.getWaynet(), targetPos); - - gotoWaypoint(targetWP); - } m_NoAniRootPosHack = false; if (model) @@ -203,7 +177,8 @@ void PlayerController::onUpdate(float deltaTime) if (isPlayerControlled()) { onUpdateForPlayer(deltaTime); - }else + } + else { m_AIHandler.npcUpdate(deltaTime); } @@ -211,8 +186,6 @@ void PlayerController::onUpdate(float deltaTime) void PlayerController::teleportToWaypoint(size_t wp) { - m_AIState.closestWaypoint = wp; - teleportToPosition(m_World.getWaynet().waypoints[wp].position); setDirection(m_World.getWaynet().waypoints[wp].direction); @@ -231,13 +204,11 @@ void PlayerController::teleportToPosition(const Math::float3& pos) //getModelVisual()->setAnimation(ModelVisual::Idle); } - void PlayerController::gotoWaypoint(World::Waynet::WaypointIndex wp) { m_PathFinder.startNewRouteTo(getEntityTransform().Translation(), wp); } - void PlayerController::gotoVob(Handle::EntityHandle vob) { m_PathFinder.startNewRouteTo(getEntityTransform().Translation(), vob); @@ -254,7 +225,7 @@ void PlayerController::travelPath() Pathfinder::Instruction inst = m_PathFinder.updateToNextInstructionToTarget(positionNow); - if(!m_PathFinder.isTargetReachedByPosition(positionNow, inst.targetPosition)) + if (!m_PathFinder.isTargetReachedByPosition(positionNow, inst.targetPosition)) { // Turn towards target setDirection(inst.targetPosition - positionNow); @@ -264,43 +235,8 @@ void PlayerController::travelPath() m_AIHandler.setTargetMovementState(EMovementState::Forward); } - void PlayerController::onDebugDraw() { - /*if (!m_MoveState.currentPath.empty()) - { - Render::debugDrawPath(m_World.getWaynet(), m_MoveState.currentPath); - }*/ - - // Math::float3 to = getEntityTransform().Translation() + Math::float3(0.0f, -1.0f, 0.0f); - // Math::float3 from = getEntityTransform().Translation() + Math::float3(0.0f, 1.0f, 0.0f); - // - // Physics::RayTestResult hit = m_World.getPhysicsSystem().raytrace(from, to, Physics::CollisionShape::CT_WorldMesh); - // - // if (hit.hasHit) - // { - // ddDrawAxis(hit.hitPosition.x, hit.hitPosition.y, hit.hitPosition.z); - // - // float shadow = m_World.getWorldMesh().interpolateTriangleShadowValue(hit.hitTriangleIndex, hit.hitPosition); - // - // if(getModelVisual()) - // getModelVisual()->setShadowValue(shadow); - // - // Math::float3 v3[3]; - // ZenLoad::zCMaterialData data = m_World.getWorldMesh().GetMatData(hit.hitTriangleIndex); - // uint8_t matgroup; - // m_World.getWorldMesh().getTriangle(hit.hitTriangleIndex, v3, matgroup); - // // if (isPlayerControlled()) - // // { - // // LogInfo() << "matgroup : " << std::bitset<8>(data.matGroup); - // // LogInfo() << "matname: " << data.matName; - // // } - // for(int i=0;i<3;i++) - // { - // ddDrawAxis(v3[i].x, v3[i].y, v3[i].z); - // } - // } - if (isPlayerControlled()) { VobTypes::NpcVobInformation npc = VobTypes::asNpcVob(m_World, m_Entity); @@ -345,269 +281,74 @@ void PlayerController::onDebugDraw() void PlayerController::unequipItem(Daedalus::GameState::ItemHandle item) { - // Get item - Daedalus::GEngineClasses::C_Item& itemData = m_World.getScriptEngine().getGameState().getItem(item); - ModelVisual* model = getModelVisual(); - - EModelNode node = EModelNode::None; - - if ((itemData.mainflag & Daedalus::GEngineClasses::C_Item::ITM_CAT_EQUIPABLE) == 0) - return; // Can't equip - - // TODO: Don't forget if an item is already unequipped before executing stat changing script-code! - - // Put into set of all equipped items first, then differentiate between the item-types - m_EquipmentState.equippedItemsAll.erase(item); - - if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_2HD_AXE) != 0) - { - node = EModelNode::Longsword; - - // Take off 2h weapon - m_EquipmentState.equippedItems.equippedWeapon2h.invalidate(); - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_2HD_SWD) != 0) - { - node = EModelNode::Longsword; - - // Take off 2h weapon - m_EquipmentState.equippedItems.equippedWeapon2h.invalidate(); - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_CROSSBOW) != 0) - { - node = EModelNode::Crossbow; - - // Take off crossbow - m_EquipmentState.equippedItems.equippedCrossBow.invalidate(); - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_BOW) != 0) - { - node = EModelNode::Bow; - - // Take off bow - m_EquipmentState.equippedItems.equippedBow.invalidate(); - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_SWD) != 0) - { - node = EModelNode::Sword; - - // Take off 1h weapon - m_EquipmentState.equippedItems.equippedWeapon1h.invalidate(); - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_AXE) != 0) - { - node = EModelNode::Sword; - - // Take off 1h weapon - m_EquipmentState.equippedItems.equippedWeapon1h.invalidate(); - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_AMULET) != 0) - { - node = EModelNode::None; - - // Take off amulet - m_EquipmentState.equippedItems.equippedAmulet.invalidate(); - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_RING) != 0) - { - node = EModelNode::None; - - // Take off ring - m_EquipmentState.equippedItems.equippedRings.erase(item); - } - else if ((itemData.mainflag & Daedalus::GEngineClasses::C_Item::ITM_CAT_RUNE) != 0 || (itemData.mainflag & Daedalus::GEngineClasses::C_Item::ITM_CAT_MAGIC) != 0) - { - node = EModelNode::None; - - // Take off our rune/scroll - m_EquipmentState.equippedItems.equippedRunes.erase(item); - } - - // Show visual on the npc-model - if (node != EModelNode::None) - model->setNodeVisual("", node); + m_CharacterEquipment.unequipItem(item); } void PlayerController::equipItem(Daedalus::GameState::ItemHandle item) { - // Get item - Daedalus::GEngineClasses::C_Item& itemData = m_World.getScriptEngine().getGameState().getItem(item); - ModelVisual* model = getModelVisual(); - - if ((itemData.mainflag & Daedalus::GEngineClasses::C_Item::ITM_CAT_EQUIPABLE) == 0) - return; // Can't equip - - EModelNode node = EModelNode::None; - - // TODO: Don't forget if an item is already equipped before executing stat changing script-code! - - // Put into set of all equipped items first, then differentiate between the item-types - m_EquipmentState.equippedItemsAll.insert(item); - - if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_2HD_AXE) != 0) - { - node = EModelNode::Longsword; - - // Take of any 1h weapon - m_EquipmentState.equippedItems.equippedWeapon1h.invalidate(); - model->setNodeVisual("", EModelNode::Sword); - - // Put on our 2h weapon - m_EquipmentState.equippedItems.equippedWeapon2h = item; - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_2HD_SWD) != 0) - { - node = EModelNode::Longsword; - - // Take of any 1h weapon - m_EquipmentState.equippedItems.equippedWeapon1h.invalidate(); - model->setNodeVisual("", EModelNode::Sword); - - // Put on our 2h weapon - m_EquipmentState.equippedItems.equippedWeapon2h = item; - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_CROSSBOW) != 0) - { - node = EModelNode::Crossbow; - - // Take off a bow - m_EquipmentState.equippedItems.equippedBow.invalidate(); - model->setNodeVisual("", EModelNode::Bow); - - // Put on our crossbow - m_EquipmentState.equippedItems.equippedCrossBow = item; - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_BOW) != 0) - { - node = EModelNode::Bow; - - // Take off a crossbow - m_EquipmentState.equippedItems.equippedCrossBow.invalidate(); - model->setNodeVisual("", EModelNode::Crossbow); - - // Put on our bow - m_EquipmentState.equippedItems.equippedBow = item; - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_SWD) != 0) - { - node = EModelNode::Sword; - - // Take of any 2h weapon - m_EquipmentState.equippedItems.equippedWeapon2h.invalidate(); - model->setNodeVisual("", EModelNode::Longsword); - - // Put on our 1h weapon - m_EquipmentState.equippedItems.equippedWeapon1h = item; - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_AXE) != 0) - { - node = EModelNode::Sword; - - // Take of any 2h weapon - m_EquipmentState.equippedItems.equippedWeapon2h.invalidate(); - model->setNodeVisual("", EModelNode::Longsword); - - // Put on our 1h weapon - m_EquipmentState.equippedItems.equippedWeapon1h = item; - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_AMULET) != 0) - { - node = EModelNode::None; - - // Put on our amulet - m_EquipmentState.equippedItems.equippedAmulet = item; - } - else if ((itemData.flags & Daedalus::GEngineClasses::C_Item::ITEM_RING) != 0) - { - node = EModelNode::None; - - // Put on our ring - m_EquipmentState.equippedItems.equippedRings.insert(item); - } - else if ((itemData.mainflag & Daedalus::GEngineClasses::C_Item::ITM_CAT_RUNE) != 0 || (itemData.mainflag & Daedalus::GEngineClasses::C_Item::ITM_CAT_MAGIC) != 0) - { - node = EModelNode::None; - - // Put on our rune/scroll - m_EquipmentState.equippedItems.equippedRunes.insert(item); - } + m_CharacterEquipment.equipItem(item); +} - // Show visual on the npc-model - if (node != EModelNode::None) - model->setNodeVisual(itemData.visual, node); +bool Logic::PlayerController::hasEquippedMeleeWeapon() const +{ + return m_CharacterEquipment.hasMeleeWeaponEquipped(); } Daedalus::GameState::ItemHandle PlayerController::drawWeaponMelee(bool forceFist) { + using Daedalus::GameState::ItemHandle; + using Slot = CharacterEquipment::Slot; + using WeaponKind = CharacterEquipment::WeaponKind; + // Check if we already have a weapon in our hands if (m_EquipmentState.weaponMode != EWeaponMode::WeaponNone) return m_EquipmentState.activeWeapon; - ModelVisual* model = getModelVisual(); - // Remove anything that was active before putting something new there m_EquipmentState.activeWeapon.invalidate(); - // Check what kind of weapon we got here - if (!forceFist && m_EquipmentState.equippedItems.equippedWeapon1h.isValid()) - { - m_EquipmentState.activeWeapon = m_EquipmentState.equippedItems.equippedWeapon1h; - m_EquipmentState.weaponMode = EWeaponMode::Weapon1h; - } - else if (!forceFist && m_EquipmentState.equippedItems.equippedWeapon2h.isValid()) + if (forceFist) { - m_EquipmentState.activeWeapon = m_EquipmentState.equippedItems.equippedWeapon2h; - m_EquipmentState.weaponMode = EWeaponMode::Weapon2h; + m_EquipmentState.weaponMode = EWeaponMode::WeaponFist; } else { - m_EquipmentState.weaponMode = EWeaponMode::WeaponFist; + ItemHandle equippedWeapon = m_CharacterEquipment.getItemInSlot(Slot::MELEE); + m_EquipmentState.activeWeapon = equippedWeapon; + + switch (m_CharacterEquipment.getWeaponKindOfItem(equippedWeapon)) + { + case WeaponKind::MELEE_1H: + m_EquipmentState.weaponMode = EWeaponMode::Weapon1h; + break; + + case WeaponKind::MELEE_2H: + m_EquipmentState.weaponMode = EWeaponMode::Weapon2h; + break; + + default: + // Not a melee weapon? + m_EquipmentState.weaponMode = EWeaponMode::WeaponNone; + break; + } } - // Move the visual if (m_EquipmentState.activeWeapon.isValid()) - { - // Get actual data of the weapon we are going to draw - Daedalus::GEngineClasses::C_Item& itemData = m_World.getScriptEngine().getGameState().getItem( - m_EquipmentState.activeWeapon); - - // Clear the possible on-body-visuals first - model->setNodeVisual("", EModelNode::Lefthand); - model->setNodeVisual("", EModelNode::Righthand); - model->setNodeVisual("", EModelNode::Sword); - model->setNodeVisual("", EModelNode::Longsword); - - // Put visual into hand - // TODO: Listen to ani-events for this! - model->setNodeVisual(itemData.visual, EModelNode::Righthand); - } + m_CharacterEquipment.putMeleeWeaponInCharactersHand(); - // Couldn't draw anything return m_EquipmentState.activeWeapon; } void PlayerController::undrawWeapon(bool force) { - ModelVisual* model = getModelVisual(); - // TODO: Listen to ani-events for this! // TODO: Even do an animation for this! // TODO: Implement force-flag - // Clear hands - model->setNodeVisual("", EModelNode::Lefthand); - model->setNodeVisual("", EModelNode::Righthand); - m_EquipmentState.weaponMode = EWeaponMode::WeaponNone; - - // activeWeapon should be only invalid when using fists - if (m_EquipmentState.activeWeapon.isValid()) - { - // reequip the currently active item - equipItem(m_EquipmentState.activeWeapon); + m_CharacterEquipment.removeItemInCharactersHandAndShowWeaponsOnBody(); - // Remove active weapon - m_EquipmentState.activeWeapon.invalidate(); - } + m_EquipmentState.weaponMode = EWeaponMode::WeaponNone; + m_EquipmentState.activeWeapon.invalidate(); } ModelVisual* PlayerController::getModelVisual() @@ -622,7 +363,9 @@ void PlayerController::placeOnSurface(const Physics::RayTestResult& hit) { if (DEBUG_PLAYER) { - LogInfo() << (int)getMaterial(hit.hitTriangleIndex) << ", Placing hero at position: " << hit.hitPosition.x << ", " << hit.hitPosition.y << ", " << hit.hitPosition.z; + LogInfo() << (int)m_World.getWorldMesh().getMaterialGroupOfTriangle(hit.hitTriangleIndex) + << ", Placing hero at position: " + << hit.hitPosition.x << ", " << hit.hitPosition.y << ", " << hit.hitPosition.z; } Math::Matrix m = getEntityTransform(); @@ -712,7 +455,7 @@ void PlayerController::placeOnGround() highestHitY = result.hitPosition.y; highestHitSurface = result; } - auto material = getMaterial(result.hitTriangleIndex); + auto material = m_World.getWorldMesh().getMaterialGroupOfTriangle(result.hitTriangleIndex); if (result.hitFlags == Physics::CollisionShape::CT_Object) material = ZenLoad::MaterialGroup::UNDEF; // we don't want underlying worldmesh material in this case if (material == ZenLoad::MaterialGroup::WATER) { @@ -725,18 +468,14 @@ void PlayerController::placeOnGround() closestResult = newDistance; } } - shallowWater = closestHitGroundSurface.hitPosition.y < waterHitSurface.hitPosition.y && waterHitSurface.hitPosition.y <= highestHitSurface.hitPosition.y; + auto manageState = [&]() { if (fellThrough) { placeOnSurface(highestHitSurface); return; } - if ((m_isSwimming = shallowWater && m_MoveState.ground.waterDepth > m_swimThreshold)) - { - placeOnSurface(waterHitSurface); - return; - } + placeOnSurface(closestHitGroundSurface); }; manageState(); @@ -791,12 +530,12 @@ void PlayerController::onUpdateByInput(float deltaTime) if (m_World.getEngine()->getHud().isMenuActive()) resetKeyStates(); - // Stand up if wounded and forward is pressed + // Stand up if wounded if (getModelVisual()->isAnimPlaying("S_WOUNDEDB") && getBodyState() == EBodyState::BS_UNCONSCIOUS) { // Only stand up if the unconscious-state has ended (aka. is not valid anymore) // Otherwise, the player would fall down immediately - if (m_isForward && m_AIStateMachine.isStateActive()) + if (m_AIStateMachine.isStateActive()) { // FIXME: End UNCONSCIOUS-state here @@ -822,309 +561,6 @@ void PlayerController::onUpdateByInput(float deltaTime) getModelVisual()->getAnimationHandler().setSpeedMultiplier(moveMod); m_AIHandler.playerUpdate(deltaTime); - return; - - // FIXME: Temporary test-code - static bool lastDraw = false; - - /* -#define SINGLE_ACTION_KEY(key, fn) { \ - static bool last = false; \ - if(inputGetKeyState(key) && !last)\ - last = true; \ - else if(!inputGetKeyState(key) && last){\ - last = false;\ - fn();\ - } } - - SINGLE_ACTION_KEY(entry::Key::KeyK, [&](){ - // Let all near NPCs draw their weapon - std::set nearNPCs = m_World.getScriptEngine().getNPCsInRadius(getEntityTransform().Translation(), 10.0f); - - for(const Handle::EntityHandle& h : nearNPCs) - { - VobTypes::NpcVobInformation npc = VobTypes::asNpcVob(m_World, h); - VobTypes::NPC_DrawMeleeWeapon(npc); - } - }); - - SINGLE_ACTION_KEY(entry::Key::KeyJ, [&](){ - // Let all near NPCs draw their weapon - std::set nearNPCs = m_World.getScriptEngine().getNPCsInRadius(getEntityTransform().Translation(), 10.0f); - - for(const Handle::EntityHandle& h : nearNPCs) - { - VobTypes::NpcVobInformation npc = VobTypes::asNpcVob(m_World, h); - VobTypes::NPC_UndrawWeapon(npc); - } - }); - - SINGLE_ACTION_KEY(entry::Key::KeyH, [&](){ - // Let all near NPCs draw their weapon - std::set nearNPCs = m_World.getScriptEngine().getNPCsInRadius(getEntityTransform().Translation(), 10.0f); - - for(const Handle::EntityHandle& h : nearNPCs) - { - VobTypes::NpcVobInformation npc = VobTypes::asNpcVob(m_World, h); - npc.playerController->attackFront(); - } - }); - */ - if (m_isDrawWeaponMelee) - { - if (!lastDraw) - { - lastDraw = true; - - if (m_EquipmentState.activeWeapon.isValid()) - undrawWeapon(); - else - drawWeaponMelee(); - } - - // Don't overwrite the drawing animation - return; - } - else if (!m_isDrawWeaponMelee && lastDraw) - { - lastDraw = false; - } - - m_NoAniRootPosHack = false; - - if (m_EquipmentState.weaponMode == EWeaponMode::WeaponNone) - { - static std::string lastMovementAni = ""; - auto manageAnimation = [&](ModelVisual::EModelAnimType groundAniType, ModelVisual::EModelAnimType waterAniType) { - if (getSurfaceMaterial() == ZenLoad::MaterialGroup::WATER) - { - if (m_isSwimming) - { - model->setAnimation(waterAniType); - } - else - { - model->setAnimation(groundAniType); - } - } - else if (getSurfaceMaterial() != ZenLoad::MaterialGroup::UNDEF) - { - model->setAnimation(groundAniType); - } - else - { - //TODO this happens more than it should, there seems to be too much undefined materials, find out why - model->setAnimation(groundAniType); // Ground animation is the default, we don't want the NPCs to start swimming in soil - } - if (getModelVisual()->getAnimationHandler().getActiveAnimationPtr()) - lastMovementAni = getModelVisual()->getAnimationHandler().getActiveAnimationPtr()->m_Name; - m_NoAniRootPosHack = true; - }; - if (m_isStrafeLeft) - { - manageAnimation(ModelVisual::EModelAnimType::StrafeLeft, ModelVisual::EModelAnimType::SwimTurnLeft); - } - else if (m_isStrafeRight) - { - manageAnimation(ModelVisual::EModelAnimType::StrafeRight, ModelVisual::EModelAnimType::SwimTurnRight); - } - else if (m_isTurnLeft && !m_isForward) - { - manageAnimation(ModelVisual::EModelAnimType::TurnLeft, ModelVisual::EModelAnimType::SwimTurnLeft); - } - else if (m_isTurnRight && !m_isForward) - { - manageAnimation(ModelVisual::EModelAnimType::TurnRight, ModelVisual::EModelAnimType::SwimTurnRight); - } - else if (m_isForward) - { - manageAnimation(m_MoveState.ground.waterDepth > m_wadeThreshold ? ModelVisual::EModelAnimType::Wade : ModelVisual::EModelAnimType::Run, ModelVisual::EModelAnimType::SwimF); - } - else if (m_isBackward) - { - manageAnimation(ModelVisual::EModelAnimType::Backpedal, ModelVisual::EModelAnimType::SwimB); - } - // else if(inputGetKeyState(entry::Key::KeyQ)) - // { - // model->setAnimation(ModelVisual::EModelAnimType::AttackFist); - // } - else if (getModelVisual()->getAnimationHandler().getActiveAnimationPtr() && getModelVisual()->getAnimationHandler().getActiveAnimationPtr()->m_Name == lastMovementAni) - { - manageAnimation(ModelVisual::EModelAnimType::Idle, ModelVisual::EModelAnimType::Swim); - m_NoAniRootPosHack = true; - } - } - // else - // { - // std::map> aniMap = - // { - // {EWeaponMode::Weapon1h, { ModelVisual::EModelAnimType::Attack1h_L, - // ModelVisual::EModelAnimType::Attack1h_R, - // ModelVisual::EModelAnimType::Run1h, - // ModelVisual::EModelAnimType::Backpedal1h, - // ModelVisual::EModelAnimType::Attack1h, - // ModelVisual::EModelAnimType::Idle1h}}, - - // {EWeaponMode::Weapon2h, { ModelVisual::EModelAnimType::Attack2h_L, - // ModelVisual::EModelAnimType::Attack2h_R, - // ModelVisual::EModelAnimType::Run2h, - // ModelVisual::EModelAnimType::Backpedal2h, - // ModelVisual::EModelAnimType::Attack2h, - // ModelVisual::EModelAnimType::Idle2h}}, - - // {EWeaponMode::WeaponBow, { ModelVisual::EModelAnimType::IdleBow, - // ModelVisual::EModelAnimType::IdleBow, - // ModelVisual::EModelAnimType::RunBow, - // ModelVisual::EModelAnimType::BackpedalBow, - // ModelVisual::EModelAnimType::AttackBow, - // ModelVisual::EModelAnimType::IdleBow}}, - - // {EWeaponMode::WeaponCrossBow, { ModelVisual::EModelAnimType::IdleCBow, - // ModelVisual::EModelAnimType::IdleCBow, - // ModelVisual::EModelAnimType::RunCBow, - // ModelVisual::EModelAnimType::BackpedalCBow, - // ModelVisual::EModelAnimType::AttackCBow, - // ModelVisual::EModelAnimType::IdleCBow}} - // }; - - // if(inputGetKeyState(entry::Key::KeyA)) - // { - // model->setAnimation(aniMap[m_EquipmentState.weaponMode][0]); - // } - // else if(inputGetKeyState(entry::Key::KeyD)) - // { - // model->setAnimation(aniMap[m_EquipmentState.weaponMode][1]); - // } - // else if(inputGetKeyState(entry::Key::KeyW)) - // { - // model->setAnimation(aniMap[m_EquipmentState.weaponMode][2]); - // } - // else if(inputGetKeyState(entry::Key::KeyS)) - // { - // model->setAnimation(aniMap[m_EquipmentState.weaponMode][3]); - // } - // else if(inputGetKeyState(entry::Key::KeyQ)) - // { - // model->setAnimation(aniMap[m_EquipmentState.weaponMode][4]); - // } - // else { - // model->setAnimation(aniMap[m_EquipmentState.weaponMode][5]); - // } - // } - - float yaw = 0.0f; - const float turnSpeed = 2.5f; - - if (!m_AIState.usedMob.isValid()) - { - if (m_isTurnLeft) - { - yaw += turnSpeed * deltaTime; - m_NoAniRootPosHack = true; - } - else if (m_isTurnRight) - { - yaw -= turnSpeed * deltaTime; - m_NoAniRootPosHack = true; - } - } - - // No direction key pressed - if (!m_NoAniRootPosHack) - return; - - // Apply animation-velocity - Math::float3 rootNodeVel = model->getAnimationHandler().getRootNodeVelocityTotal() * deltaTime; - - float angle = atan2(m_MoveState.direction.z, m_MoveState.direction.x); - m_MoveState.direction = Math::float3(cos(angle + yaw), 0, sin(angle + yaw)); - angle = atan2(m_MoveState.direction.z, m_MoveState.direction.x); - - m_MoveState.position += Math::Matrix::CreateRotationY(-angle + Math::PI * 0.5f) * rootNodeVel; - - setDirection(m_MoveState.direction); - - //Math::Matrix newTransform = Math::Matrix::CreateTranslation(m_MoveState.position) * Math::Matrix::CreateRotationY(angle + yaw); - //setEntityTransform(newTransform); - - placeOnGround(); - resetKeyStates(); -} - -void PlayerController::attackFront() -{ - if (m_EquipmentState.weaponMode == EWeaponMode::WeaponNone) - return; - - ModelVisual::EModelAnimType type = ModelVisual::EModelAnimType::NUM_ANIMATIONS; - switch (m_EquipmentState.weaponMode) - { - case EWeaponMode::Weapon1h: - type = ModelVisual::EModelAnimType::Attack1h; - break; - case EWeaponMode::Weapon2h: - type = ModelVisual::EModelAnimType::Attack2h; - break; - case EWeaponMode::WeaponBow: - type = ModelVisual::EModelAnimType::AttackBow; - break; - case EWeaponMode::WeaponCrossBow: - type = ModelVisual::EModelAnimType::AttackCBow; - break; - case EWeaponMode::WeaponFist: - type = ModelVisual::EModelAnimType::AttackFist; - break; - //case EWeaponMode::WeaponMagic: type = ModelVisual::EModelAnimType::AttackMagic; break; // TODO: Magic - default: - break; - } - - if (type != ModelVisual::EModelAnimType::NUM_ANIMATIONS) - getModelVisual()->playAnimation(type); -} - -void PlayerController::attackLeft() -{ - if (m_EquipmentState.weaponMode == EWeaponMode::WeaponNone) - return; - - ModelVisual::EModelAnimType type = ModelVisual::EModelAnimType::NUM_ANIMATIONS; - switch (m_EquipmentState.weaponMode) - { - case EWeaponMode::Weapon1h: - type = ModelVisual::EModelAnimType::Attack1h_L; - break; - case EWeaponMode::Weapon2h: - type = ModelVisual::EModelAnimType::Attack2h_L; - break; - default: - break; - } - - if (type != ModelVisual::EModelAnimType::NUM_ANIMATIONS) - getModelVisual()->playAnimation(type); -} - -void PlayerController::attackRight() -{ - if (m_EquipmentState.weaponMode == EWeaponMode::WeaponNone) - return; - - ModelVisual::EModelAnimType type = ModelVisual::EModelAnimType::NUM_ANIMATIONS; - switch (m_EquipmentState.weaponMode) - { - case EWeaponMode::Weapon1h: - type = ModelVisual::EModelAnimType::Attack1h_R; - break; - case EWeaponMode::Weapon2h: - type = ModelVisual::EModelAnimType::Attack2h_L; - break; - default: - break; - } - - if (type != ModelVisual::EModelAnimType::NUM_ANIMATIONS) - getModelVisual()->playAnimation(type); } void PlayerController::onMessage(SharedEMessage message, Handle::EntityHandle sourceVob) @@ -1205,7 +641,7 @@ bool PlayerController::EV_Movement(std::shared_ptr(symRoutine); @@ -1858,8 +1289,8 @@ bool PlayerController::useItem(Daedalus::GameState::ItemHandle item) changeAttribute(Daedalus::GEngineClasses::C_Npc::EATR_HITPOINTS, data.nutrition); } - // Weapon? - if ((data.mainflag & Daedalus::GEngineClasses::C_Item::ITM_CAT_NF) != 0 || (data.mainflag & Daedalus::GEngineClasses::C_Item::ITM_CAT_FF) != 0 || (data.mainflag & Daedalus::GEngineClasses::C_Item::ITM_CAT_ARMOR) != 0 || (data.mainflag & Daedalus::GEngineClasses::C_Item::ITM_CAT_MAGIC) != 0) + // Equipment? + if (m_CharacterEquipment.isItemEquipable(item)) { // FIXME: Hack to only allow equipping when no weapon is drawn if (getWeaponMode() == EWeaponMode::WeaponNone) @@ -2050,16 +1481,17 @@ void PlayerController::exportPart(json& j) m_Inventory.exportInventory(j["inventory"]); // Export equipped items + m_CharacterEquipment.exportSlots(j["equipment"]); { - j["equipped"] = json::array(); - for (auto item : m_EquipmentState.equippedItemsAll) - { - // Write instance of the equipped item - Daedalus::GEngineClasses::C_Item& data = m_World.getScriptEngine().getGameState().getItem(item); - std::string instanceName = m_World.getScriptEngine().getVM().getDATFile().getSymbolByIndex(data.instanceSymbol).name; + // j["equipped"] = json::array(); + // for (auto item : m_EquipmentState.equippedItemsAll) + // { + // // Write instance of the equipped item + // Daedalus::GEngineClasses::C_Item& data = m_World.getScriptEngine().getGameState().getItem(item); + // std::string instanceName = m_World.getScriptEngine().getVM().getDATFile().getSymbolByIndex(data.instanceSymbol).name; - j["equipped"].push_back(instanceName); - } + // j["equipped"].push_back(instanceName); + // } } // export refusetalktime @@ -2131,18 +1563,7 @@ void PlayerController::importObject(const json& j, bool noTransform) m_Inventory.importInventory(j["inventory"]); // Import equipments - { - Inventory& inv = m_Inventory; - - for (const std::string& sym : j["equipped"]) - { - Daedalus::GameState::ItemHandle h = inv.getItem(sym); - - assert(h.isValid()); // Item to equip MUST be inside the inventory - - equipItem(h); - } - } + m_CharacterEquipment.importSlots(j["equipment"]); // import refusetalktime this->setRefuseTalkTime(static_cast(j["refusetalktime"])); @@ -2167,10 +1588,6 @@ Handle::EntityHandle PlayerController::importPlayerController(World::WorldInstan { unsigned int instanceSymbol = j["scriptObj"]["instanceSymbol"]; - /*std::string name = j["scriptObj"]["name"][0]; - if(name != "Diego" && name != "ich") - return Handle::EntityHandle();*/ - // Create npc Handle::EntityHandle e = VobTypes::Wld_InsertNpc(world, instanceSymbol); @@ -2288,20 +1705,11 @@ void PlayerController::updateStatusScreen(UI::Menu_Status& statsScreen) statsScreen.setLearnPoints(stats.lp); } -ZenLoad::MaterialGroup PlayerController::getMaterial(uint32_t triangleIdx) -{ - Math::float3 v3[3]; - uint8_t matgroup; - // Beware! If triangle index is given such that the triangle is a building triangle of a VOB, this function will return material of the underlying worldmesh!!! - m_World.getWorldMesh().getTriangle(triangleIdx, v3, matgroup); - return static_cast(matgroup); -} - ZenLoad::MaterialGroup PlayerController::getSurfaceMaterial() { if (m_MoveState.ground.successful) { - return getMaterial(m_MoveState.ground.triangleIndex); + return m_World.getWorldMesh().getMaterialGroupOfTriangle(m_MoveState.ground.triangleIndex); } else { @@ -2343,13 +1751,7 @@ void PlayerController::traceDownNPCGround() { auto diff = std::abs(entityPos.y - a.hitPosition.y); - if (ZenLoad::MaterialGroup::WATER == getMaterial(a.hitTriangleIndex)) - { - resultWater = a; - waterSurfacePos = a.hitPosition.y; - waterMatFound = true; // found water material - } - else if (closestGroundSurfacePos > diff) + if (closestGroundSurfacePos > diff) { result = a; closestGroundSurfacePos = diff; @@ -2393,21 +1795,15 @@ void PlayerController::traceDownNPCGround() void PlayerController::resetKeyStates() { - m_isStrafeLeft = false; - m_isStrafeRight = false; - m_isForward = false; - m_isBackward = false; - m_isTurnLeft = false; - m_isTurnRight = false; m_MoveSpeed1 = false; m_MoveSpeed2 = false; } -void PlayerController::updatePfxPosition(const pfxEvent &e) +void PlayerController::updatePfxPosition(const pfxEvent& e) { Vob::VobInformation vob = Vob::asVob(m_World, e.entity); - auto &boneTransforms = getNpcAnimationHandler().getAnimHandler().getObjectSpaceTransforms(); - auto &transform = getEntityTransform(); + auto& boneTransforms = getNpcAnimationHandler().getAnimHandler().getObjectSpaceTransforms(); + auto& transform = getEntityTransform(); size_t nodeIndex = getModelVisual()->findNodeIndex(e.bodyPosition); auto position = transform * boneTransforms[nodeIndex]; Vob::setTransform(vob, std::move(position)); @@ -2416,9 +1812,9 @@ void PlayerController::updatePfxPosition(const pfxEvent &e) void PlayerController::updatePfx() { //First remove all invalid handles / emitter that are in "canBeRemoved" state (all particles are dead) - for(auto it = m_activePfxEvents.begin(); itcanBeRemoved()) + if (((PfxVisual*)Vob::asVob(m_World, (*it).entity).visual)->canBeRemoved()) { m_World.removeEntity((*it).entity); it = m_activePfxEvents.erase(it); @@ -2426,7 +1822,7 @@ void PlayerController::updatePfx() else { //If pfx is attached to body, update the position - if((*it).isAttached) + if ((*it).isAttached) updatePfxPosition(*it); ++it; @@ -2465,7 +1861,7 @@ void PlayerController::AniEvent_SFX(const ZenLoad::zCModelScriptEventSfx& sfx) } } - if(!sfx.m_EmptySlot && m_World.getAudioWorld().soundIsPlaying(m_MainNoiseSoundSlot)) + if (!sfx.m_EmptySlot && m_World.getAudioWorld().soundIsPlaying(m_MainNoiseSoundSlot)) { // If emptyslot is not set, the currently played sound shall be stopped m_World.getAudioWorld().stopSound(m_MainNoiseSoundSlot); @@ -2476,11 +1872,11 @@ void PlayerController::AniEvent_SFX(const ZenLoad::zCModelScriptEventSfx& sfx) auto ticket = m_World.getAudioWorld().playSound(sfx.m_Name, getEntityTransform().Translation(), range); - if(!sfx.m_EmptySlot) + if (!sfx.m_EmptySlot) { // If emptyslot is not set, the currently played sound shall be stopped - if(m_World.getAudioWorld().soundIsPlaying(m_MainNoiseSoundSlot)) + if (m_World.getAudioWorld().soundIsPlaying(m_MainNoiseSoundSlot)) m_World.getAudioWorld().stopSound(m_MainNoiseSoundSlot); m_MainNoiseSoundSlot = ticket; @@ -2492,7 +1888,7 @@ void PlayerController::AniEvent_SFXGround(const ZenLoad::zCModelScriptEventSfx& if (m_MoveState.ground.successful) { // Play sound depending on ground type - ZenLoad::MaterialGroup mat = getMaterial(m_MoveState.ground.triangleIndex); + ZenLoad::MaterialGroup mat = getSurfaceMaterial(); float range = sfx.m_Range != 0.0f ? sfx.m_Range : DEFAULT_CHARACTER_SOUND_RANGE; @@ -2500,11 +1896,11 @@ void PlayerController::AniEvent_SFXGround(const ZenLoad::zCModelScriptEventSfx& auto ticket = m_World.getAudioWorld().playSoundVariantRandom(soundfile, getEntityTransform().Translation(), range); - if(!sfx.m_EmptySlot) + if (!sfx.m_EmptySlot) { // If emptyslot is not set, the currently played sound shall be stopped - if(m_World.getAudioWorld().soundIsPlaying(m_MainNoiseSoundSlot)) + if (m_World.getAudioWorld().soundIsPlaying(m_MainNoiseSoundSlot)) m_World.getAudioWorld().stopSound(m_MainNoiseSoundSlot); m_MainNoiseSoundSlot = ticket; @@ -2518,41 +1914,42 @@ void PlayerController::AniEvent_Tag(const ZenLoad::zCModelScriptEventTag& tag) } void PlayerController::AniEvent_PFX(const ZenLoad::zCModelScriptEventPfx& pfx) { - if(!m_World.getPfxManager().hasPFX(pfx.m_Name)) + if (!m_World.getPfxManager().hasPFX(pfx.m_Name)) { return; } pfxEvent event = {Vob::constructVob(m_World), pfx.m_Pos, pfx.m_isAttached, pfx.m_Num}; //From world of gothic animation events - if(event.bodyPosition.empty()) + if (event.bodyPosition.empty()) { event.bodyPosition = "BIP01"; } m_activePfxEvents.push_back(std::move(event)); Vob::VobInformation vob = Vob::asVob(m_World, m_activePfxEvents.back().entity); - Vob::setVisual(vob, pfx.m_Name+".PFX"); - Vob::setTransform(vob, getEntityTransform()); + Vob::setVisual(vob, pfx.m_Name + ".PFX"); + Vob::setTransform(vob, getEntityTransform()); } void PlayerController::AniEvent_PFXStop(const ZenLoad::zCModelScriptEventPfxStop& pfxStop) { - //FIXME there is the error (at icedragon at oldworld) that there are pfxStop Events when no active pfx events are present... - if(m_activePfxEvents.empty()) - LogWarn() << "No corresponding pfx Event for Stop Event of Animation " + getNpcAnimationHandler().getAnimHandler().getActiveAnimationPtr()->m_Name; + //FIXME there is the error (at icedragon at oldworld) that there are pfxStop Events when no active pfx events are present... + if (m_activePfxEvents.empty()) + LogWarn() << "No corresponding pfx Event for Stop Event of Animation " + getNpcAnimationHandler().getAnimHandler().getActiveAnimationPtr()->m_Name; //Kill pfx with the corresponding number - for (auto &pfx : m_activePfxEvents) { - if (pfx.m_Num == pfxStop.m_Num) { - Vob::VobInformation vob = Vob::asVob(m_World, pfx.entity); - PfxVisual *visual = (PfxVisual *) vob.visual; - if (visual->isDead()) { - continue; - } - visual->killPfx(); - break; - } - } - - + for (auto& pfx : m_activePfxEvents) + { + if (pfx.m_Num == pfxStop.m_Num) + { + Vob::VobInformation vob = Vob::asVob(m_World, pfx.entity); + PfxVisual* visual = (PfxVisual*)vob.visual; + if (visual->isDead()) + { + continue; + } + visual->killPfx(); + break; + } + } } World::Waynet::WaypointIndex PlayerController::getClosestWaypoint() @@ -2582,7 +1979,7 @@ World::Waynet::WaypointIndex PlayerController::getSecondClosestWaypoint() float l2 = (position - waynet.waypoints[i].position).lengthSquared(); distances.emplace_back(l2, i); } - auto compare = [](const auto& pair1, const auto& pair2){ + auto compare = [](const auto& pair1, const auto& pair2) { return pair1.first < pair2.first; }; std::nth_element(distances.begin(), distances.begin() + 1, distances.end(), compare); @@ -2597,9 +1994,6 @@ void PlayerController::onAction(Engine::ActionType actionType, bool triggered, f switch (actionType) { - case ActionType::PlayerDrawWeaponMelee: - m_isDrawWeaponMelee = triggered; - break; case ActionType::PlayerForward: { // Increment state, if currently using a mob @@ -2616,12 +2010,8 @@ void PlayerController::onAction(Engine::ActionType actionType, bool triggered, f } } } - else - { - m_isForward = m_isForward || triggered; - } } - break; + break; case ActionType::PlayerBackward: { // Increment state, if currently using a mob @@ -2638,24 +2028,9 @@ void PlayerController::onAction(Engine::ActionType actionType, bool triggered, f } } } - else - { - m_isBackward = m_isBackward || triggered; - } } - break; - case ActionType::PlayerTurnLeft: - m_isTurnLeft = m_isTurnLeft || triggered; - break; - case ActionType::PlayerTurnRight: - m_isTurnRight = m_isTurnRight || triggered; - break; - case ActionType::PlayerStrafeLeft: - m_isStrafeLeft = m_isStrafeLeft || triggered; - break; - case ActionType::PlayerStrafeRight: - m_isStrafeRight = m_isStrafeRight || triggered; - break; + break; + case ActionType::DebugMoveSpeed: m_MoveSpeed1 = m_MoveSpeed1 || triggered; break; @@ -2665,11 +2040,11 @@ void PlayerController::onAction(Engine::ActionType actionType, bool triggered, f case ActionType::PlayerRotate: { auto dir = getDirection(); - auto deltaPhi = 0.02f * intensity; // TODO window width influences this??? + auto deltaPhi = 0.02f * intensity; // TODO window width influences this??? dir = Math::Matrix::rotatedPointAroundLine(dir, {0, 0, 0}, getEntityTransform().Up(), deltaPhi); setDirection(dir); } - break; + break; case ActionType::PlayerAction: { if (triggered) @@ -2689,8 +2064,8 @@ void PlayerController::onAction(Engine::ActionType actionType, bool triggered, f Math::Matrix& m = m_World.getEntity(h).m_WorldMatrix; float dist = (m.Translation() - - getEntityTransform().Translation()) - .lengthSquared(); + getEntityTransform().Translation()) + .lengthSquared(); if (dist < shortestDistItem && dist < 10.0f * 10.0f) { nearestItem = h; @@ -2711,8 +2086,8 @@ void PlayerController::onAction(Engine::ActionType actionType, bool triggered, f VobTypes::NpcVobInformation npc = VobTypes::asNpcVob(m_World, h); float dist = (Vob::getTransform(npc).Translation() - - getEntityTransform().Translation()) - .lengthSquared(); + getEntityTransform().Translation()) + .lengthSquared(); if (dist < shortestDistNPC) { nearestNPC = h; @@ -2731,8 +2106,8 @@ void PlayerController::onAction(Engine::ActionType actionType, bool triggered, f VobTypes::MobVobInformation vob = VobTypes::asMobVob(m_World, h); float dist = (Vob::getTransform(vob).Translation() - - getEntityTransform().Translation()) - .lengthSquared(); + getEntityTransform().Translation()) + .lengthSquared(); if (dist < shortestDistMob) { @@ -2822,14 +2197,12 @@ void PlayerController::onAction(Engine::ActionType actionType, bool triggered, f } } } - break; - + break; case ActionType::PlayerActionContinous: break; default: - assert(false); break; } } diff --git a/src/logic/PlayerController.h b/src/logic/PlayerController.h index d9d759db..a0166f4f 100644 --- a/src/logic/PlayerController.h +++ b/src/logic/PlayerController.h @@ -8,6 +8,7 @@ #include "NpcAnimationHandler.h" #include "NpcScriptState.h" #include "Pathfinder.h" +#include "CharacterEquipment.h" #include namespace UI @@ -39,7 +40,7 @@ namespace Logic const char* const PLAYER_MOB_ANOTHER_IS_USING = "PLAYER_MOB_ANOTHER_IS_USING"; const char* const PLAYER_PLUNDER_IS_EMPTY = "PLAYER_PLUNDER_IS_EMPTY"; const char* const PLAYER_RANGED_NO_AMMO = "PLAYER_RANGED_NO_AMMO"; - } + } // namespace PlayerScriptInfo /** * All kinds of things an NPC can do @@ -83,7 +84,6 @@ namespace Logic class PlayerController : public Controller { public: - using WalkMode = EventMessages::MovementMessage::WalkMode; /** @@ -97,8 +97,6 @@ namespace Logic */ EControllerType getControllerType() override { return EControllerType::PlayerController; } - void setFollowTarget(Handle::EntityHandle e) { m_RoutineState.entityTarget = e; } - /** * Called when the models visual changed */ @@ -139,11 +137,6 @@ namespace Logic void gotoVob(Handle::EntityHandle vob); void gotoPosition(const Math::float3& position); - /** - * Stops going along the current route - */ - void stopRoute(); - /** * Teleports the entity to the given waypoint * @param Waypoint index to go to @@ -264,13 +257,6 @@ namespace Logic */ Daedalus::GEngineClasses::C_Npc& getScriptInstance(); - /** - * Front/Right/Left-Attack with the current weapon - */ - void attackFront(); - void attackLeft(); - void attackRight(); - /** * @return True, if this is the currently controlled character */ @@ -392,10 +378,6 @@ namespace Logic */ ZenLoad::MaterialGroup getSurfaceMaterial(); - /** - * Returns material data of give triangle index - */ - ZenLoad::MaterialGroup getMaterial(uint32_t triangleIdx); /** * @return Item this NPC is currently interacting with */ @@ -440,10 +422,7 @@ namespace Logic /** * Check if this NPC has equipped any melee weapon */ - bool hasEquippedMeleeWeapon() const - { - return m_EquipmentState.equippedItems.equippedWeapon1h.isValid() || m_EquipmentState.equippedItems.equippedWeapon2h.isValid(); - }; + bool hasEquippedMeleeWeapon() const; /** * @param walkMode Whether we should be runnning, sneaking, etc @@ -482,25 +461,8 @@ namespace Logic */ void travelPath(); - /** - * Current routine state - */ - struct - { - /** - * Target of where the NPC should keep trying to go to - */ - Handle::EntityHandle entityTarget; - } m_RoutineState; - struct { - // Waypoint this NPC is closest to/was last positioned at - size_t closestWaypoint; - - // Waypoint the NPC is going to - size_t targetWaypoint; - // Handle to the Mob currently used by this NPC, if valid Handle::EntityHandle usedMob; @@ -511,18 +473,6 @@ namespace Logic struct { - // Path the NPC is currently trying to take (To m_AIState.targetWaypoint) - std::vector currentPath; - - // Node the NPC is currently going to on the path - size_t targetNode; - - // Percentage of how far the NPC has gotten so far on the current path - float currentPathPerc; - - // Length of the current route - float currentRouteLength; - // Where the npc currently is Math::float3 position; @@ -542,9 +492,6 @@ namespace Logic struct { - // Move speed in m/s - float moveSpeed; - // BBox for collision Math::float3 collisionBBox[2]; @@ -567,45 +514,14 @@ namespace Logic EWeaponMode weaponMode; Daedalus::GameState::ItemHandle activeWeapon; - // All equiped items. Contains weapons, rings, armor... - std::set equippedItemsAll; - - // All possible equipped items on an NPC - struct - { - std::set equippedRings; - std::set equippedRunes; - Daedalus::GameState::ItemHandle equippedWeapon1h; - Daedalus::GameState::ItemHandle equippedWeapon2h; - Daedalus::GameState::ItemHandle equippedBow; - Daedalus::GameState::ItemHandle equippedCrossBow; - Daedalus::GameState::ItemHandle equippedBelt; - Daedalus::GameState::ItemHandle equippedAmulet; - } equippedItems; } m_EquipmentState; - /** - * This players inventory - */ Inventory m_Inventory; - - /** - * State manager - */ NpcScriptState m_AIStateMachine; - - /** - * Animation handler - */ NpcAnimationHandler m_NPCAnimationHandler; - - /** - * AI/Input handler - */ NpcAIHandler m_AIHandler; - - // Route-information Pathfinder m_PathFinder; + CharacterEquipment m_CharacterEquipment; /** * refuse talk countdown @@ -615,14 +531,6 @@ namespace Logic /** * Key states */ - bool m_isDrawWeaponMelee; - bool m_isForward; - bool m_isBackward; - bool m_isTurnLeft; - bool m_isTurnRight; - bool m_isStrafeLeft; - bool m_isStrafeRight; - bool m_isSwimming; bool m_MoveSpeed1, m_MoveSpeed2; /** @@ -633,12 +541,14 @@ namespace Logic Handle::EntityHandle entity; std::string bodyPosition; bool isAttached; - int32_t m_Num; + int32_t m_Num; }; + /** * Stores active pfx handler associated with this PlayerController (one shot) */ std::vector m_activePfxEvents; + /** * Stores ended pfx handler (emitter don't spawn new particles) * After all particles are dead, updatePfx() will remove these elements from this vector @@ -649,7 +559,8 @@ namespace Logic * Updates the position of the pfxEvent * @param e pfxEvent */ - void updatePfxPosition(const pfxEvent &e); + void updatePfxPosition(const pfxEvent& e); + /** * Upates the pfx for this player (removes dead emitter) * When "ATTACH" keyword is used, the position is also changed. @@ -665,11 +576,5 @@ namespace Logic // Main noise sound slot. Other sounds using it won't play if there is already a sound playing here. Utils::Ticket m_MainNoiseSoundSlot; - - /** - * Contstants - */ - static constexpr float m_swimThreshold = 1.3; // TODO Adjust the value to reflect original game experiece - static constexpr float m_wadeThreshold = 0.5; // TODO Adjust the value to reflect original game experiece }; -} +} // namespace Logic diff --git a/src/logic/SavegameManager.cpp b/src/logic/SavegameManager.cpp index 3993f290..19240f70 100644 --- a/src/logic/SavegameManager.cpp +++ b/src/logic/SavegameManager.cpp @@ -147,7 +147,7 @@ Engine::SavegameManager::SavegameInfo SavegameManager::readSavegameInfo(int idx) bool SavegameManager::writePlayer(int idx, const std::string& playerName, const nlohmann::json& player) { - return writeFileInSlot(idx, playerName + ".json", Utils::iso_8859_1_to_utf8(player.dump())); + return writeFileInSlot(idx, playerName + ".json", Utils::iso_8859_1_to_utf8(player.dump(4))); } std::string SavegameManager::readPlayer(int idx, const std::string& playerName) diff --git a/src/logic/ScriptEngine.cpp b/src/logic/ScriptEngine.cpp index 33881657..65492125 100644 --- a/src/logic/ScriptEngine.cpp +++ b/src/logic/ScriptEngine.cpp @@ -207,6 +207,11 @@ size_t ScriptEngine::getSymbolIndexByName(const std::string& name) return m_pVM->getDATFile().getSymbolIndexByName(name); } +std::string Logic::ScriptEngine::getSymbolNameByIndex(size_t idx) const +{ + return m_pVM->getDATFile().getSymbolByIndex(idx).name; +} + void ScriptEngine::onInventoryItemInserted(Daedalus::GameState::ItemHandle item, Daedalus::GameState::NpcHandle npc) { Daedalus::GEngineClasses::C_Item& itemData = getGameState().getItem(item); diff --git a/src/logic/ScriptEngine.h b/src/logic/ScriptEngine.h index 10434721..291ef083 100644 --- a/src/logic/ScriptEngine.h +++ b/src/logic/ScriptEngine.h @@ -107,6 +107,7 @@ namespace Logic * @return Symbol-index, -1 of not found */ size_t getSymbolIndexByName(const std::string& name); + std::string getSymbolNameByIndex(size_t idx) const; /** * Checks whether the given symbol exists diff --git a/src/logic/visuals/ModelVisual.cpp b/src/logic/visuals/ModelVisual.cpp index 33f5aaeb..5cdd184c 100644 --- a/src/logic/visuals/ModelVisual.cpp +++ b/src/logic/visuals/ModelVisual.cpp @@ -654,6 +654,10 @@ void ModelVisual::updateBodyMesh() void ModelVisual::updateHeadMesh() { + // Prevents head mesh clearing for models with head inside body mesh + if (m_BodyState.headVisual.empty()) + return; + for (Handle::EntityHandle e : m_PartEntities.headMeshEntities) m_World.removeEntity(e);