diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 89c39c33..e7cc0722 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -521,6 +521,10 @@ void BaseCombatAIComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsI void BaseCombatAIComponent::SetAiState(AiState newState) { if (newState == this->m_State) return; + GameMessages::NotifyCombatAIStateChange stateMsg; + stateMsg.prevState = this->m_State; + stateMsg.newState = newState; + m_Parent->HandleMsg(stateMsg); this->m_State = newState; m_DirtyStateOrTarget = true; Game::entityManager->SerializeEntity(m_Parent); diff --git a/dGame/dComponents/ScriptComponent.cpp b/dGame/dComponents/ScriptComponent.cpp index 2c9be34b..d4a2a9d4 100644 --- a/dGame/dComponents/ScriptComponent.cpp +++ b/dGame/dComponents/ScriptComponent.cpp @@ -16,7 +16,7 @@ ScriptComponent::ScriptComponent(Entity* parent, const int32_t componentID, cons m_ScriptName = scriptName; SetScript(scriptName); - RegisterMsg(&ScriptComponent::OnGetObjectReportInfo); + Component::RegisterMsg(&ScriptComponent::OnGetObjectReportInfo); } void ScriptComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { diff --git a/dGame/dComponents/ScriptComponent.h b/dGame/dComponents/ScriptComponent.h index e79b8e91..723df739 100644 --- a/dGame/dComponents/ScriptComponent.h +++ b/dGame/dComponents/ScriptComponent.h @@ -8,6 +8,9 @@ #include "CppScripts.h" #include "Component.h" +#include "GameMessages.h" +#include +#include #include #include "eReplicaComponentType.h" @@ -42,9 +45,22 @@ public: * @param scriptName the name of the script to find */ void SetScript(const std::string& scriptName); - + bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo); + // Registers a message from a script to be listened for on the parent object + template + void RegisterMsg(ScriptClass* scriptThis, bool (ScriptClass::*scriptHandler)(Entity&, DerivedMsgType&)) { + static_assert(std::is_base_of_v, "DerivedMsgType must derive from GameMessages::GameMsg base class."); + const auto boundMsg = std::bind(scriptHandler, scriptThis, std::placeholders::_1, std::placeholders::_2); + auto* const parent = m_Parent; + const auto castWrapper = [parent, boundMsg](GameMessages::GameMsg& msg) { + return boundMsg(*parent, static_cast(msg)); + }; + DerivedMsgType msg; + m_Parent->RegisterMsg(msg.msgId, castWrapper); + } + private: /** diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index fc8a5d46..ce70a42c 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -45,6 +45,7 @@ enum class BehaviorSlot : int32_t; enum class eVendorTransactionResult : uint32_t; enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t; enum class eMissionState : int; +enum class AiState : uint32_t; enum class eCameraTargetCyclingMode : int32_t { ALLOW_CYCLE_TEAMMATES, @@ -980,5 +981,12 @@ namespace GameMessages { LWOOBJID objectID{}; LOT lot{}; }; + + struct NotifyCombatAIStateChange : public GameMsg { + NotifyCombatAIStateChange() : GameMsg(MessageType::Game::NOTIFY_COMBAT_AI_STATE_CHANGE) {} + + AiState newState{}; + AiState prevState{}; + }; }; #endif // GAMEMESSAGES_H diff --git a/dScripts/02_server/Enemy/FV/CMakeLists.txt b/dScripts/02_server/Enemy/FV/CMakeLists.txt index 5146d8d8..af3be0b4 100644 --- a/dScripts/02_server/Enemy/FV/CMakeLists.txt +++ b/dScripts/02_server/Enemy/FV/CMakeLists.txt @@ -1,4 +1,5 @@ set(DSCRIPTS_SOURCES_02_SERVER_ENEMY_FV + "DragonRonin.cpp" "FvMaelstromCavalry.cpp" "FvMaelstromDragon.cpp" PARENT_SCOPE) diff --git a/dScripts/02_server/Enemy/FV/DragonRonin.cpp b/dScripts/02_server/Enemy/FV/DragonRonin.cpp new file mode 100644 index 00000000..64dcc20e --- /dev/null +++ b/dScripts/02_server/Enemy/FV/DragonRonin.cpp @@ -0,0 +1,6 @@ +#include "DragonRonin.h" + +void DragonRonin::OnStartup(Entity* self) { + self->SetVar(u"suicideTimer", 40.0f); + CountdownDestroyAI::OnStartup(self); +} diff --git a/dScripts/02_server/Enemy/FV/DragonRonin.h b/dScripts/02_server/Enemy/FV/DragonRonin.h new file mode 100644 index 00000000..1dd45c9c --- /dev/null +++ b/dScripts/02_server/Enemy/FV/DragonRonin.h @@ -0,0 +1,7 @@ +#pragma once +#include "CountdownDestroyAI.h" + +class DragonRonin : public CountdownDestroyAI { +public: + void OnStartup(Entity* self) override; +}; diff --git a/dScripts/02_server/Enemy/General/CMakeLists.txt b/dScripts/02_server/Enemy/General/CMakeLists.txt index 5486d2b0..c685d290 100644 --- a/dScripts/02_server/Enemy/General/CMakeLists.txt +++ b/dScripts/02_server/Enemy/General/CMakeLists.txt @@ -1,6 +1,7 @@ set(DSCRIPTS_SOURCES_02_SERVER_ENEMY_GENERAL "BaseEnemyMech.cpp" "BaseEnemyApe.cpp" + "CountdownDestroyAI.cpp" "GfApeSmashingQB.cpp" "TreasureChestDragonServer.cpp" "EnemyNjBuff.cpp" diff --git a/dScripts/02_server/Enemy/General/CountdownDestroyAI.cpp b/dScripts/02_server/Enemy/General/CountdownDestroyAI.cpp new file mode 100644 index 00000000..3ca35340 --- /dev/null +++ b/dScripts/02_server/Enemy/General/CountdownDestroyAI.cpp @@ -0,0 +1,50 @@ +#include "CountdownDestroyAI.h" + +#include "BaseCombatAIComponent.h" +#include "ScriptComponent.h" + +void CountdownDestroyAI::OnStartup(Entity* self) { + CountdownStartup(*self); + auto* scriptComp = self->GetComponent(); + if (scriptComp) scriptComp->RegisterMsg(this, &CountdownDestroyAI::OnNotifyCombatAIStateChange); +} + +void CountdownDestroyAI::CountdownStartup(Entity& self) { + auto suicideTimer = self.GetVar(u"suicideTimer"); + if (suicideTimer == 0.0f) suicideTimer = 60; + self.AddTimer("Dead", suicideTimer); +} + +void CountdownDestroyAI::OnHit(Entity* self, Entity* attacker) { + if (!self->GetVar(u"ShouldBeDead")) return; + self->CancelTimer("IsBeingAttacked"); + self->AddTimer("Dead", 5.0f); +} + +void CountdownDestroyAI::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == "Dead") { + self->SetVar(u"ShouldBeDead", true); + if (self->GetVar(u"Busy")) { + self->AddTimer("IsBeingAttacked", 5.0f); + } else { + self->Smash(); + } + } else if (timerName == "IsBeingAttacked") { + self->Smash(); + } +} + +bool CountdownDestroyAI::OnNotifyCombatAIStateChange(Entity& self, GameMessages::NotifyCombatAIStateChange& notifyMsg) { + const auto curState = notifyMsg.newState; + if (curState == AiState::dead) return true; + + if (curState == AiState::aggro || curState == AiState::tether) { + self.SetVar(u"Busy", true); + } else { + self.SetVar(u"Busy", false); + if (self.GetVar(u"ShouldBeDead")) { + self.Smash(); + } + } + return true; +} diff --git a/dScripts/02_server/Enemy/General/CountdownDestroyAI.h b/dScripts/02_server/Enemy/General/CountdownDestroyAI.h new file mode 100644 index 00000000..cdcb7646 --- /dev/null +++ b/dScripts/02_server/Enemy/General/CountdownDestroyAI.h @@ -0,0 +1,13 @@ +#pragma once +#include "CppScripts.h" + +#include "GameMessages.h" + +class CountdownDestroyAI : public CppScripts::Script { +public: + void OnStartup(Entity* self) override; + void CountdownStartup(Entity& self); + void OnHit(Entity* self, Entity* attacker) override; + void OnTimerDone(Entity* self, std::string timerName) override; + bool OnNotifyCombatAIStateChange(Entity& self, GameMessages::NotifyCombatAIStateChange& msg); +}; diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index a6c3c00d..0e03c983 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -135,8 +135,11 @@ #include "FvMaelstromCavalry.h" #include "FvHorsemenTrigger.h" #include "FvFlyingCreviceDragon.h" +#include "FvDragonInstanceServer.h" #include "FvMaelstromDragon.h" +#include "DragonRonin.h" #include "FvDragonSmashingGolemQb.h" +#include "CountdownDestroyAI.h" #include "TreasureChestDragonServer.h" #include "InstanceExitTransferPlayerToLastNonInstance.h" #include "FvFreeGfNinjas.h" @@ -491,7 +494,10 @@ namespace { {"scripts\\ai\\FV\\L_ACT_NINJA_TURRET_1.lua", []() {return new ActNinjaTurret();}}, {"scripts\\02_server\\Map\\FV\\L_FV_HORSEMEN_TRIGGER.lua", []() {return new FvHorsemenTrigger();}}, {"scripts\\ai\\FV\\L_FV_FLYING_CREVICE_DRAGON.lua", []() {return new FvFlyingCreviceDragon();}}, + {"scripts\\ai\\FV\\Dragon_Instance\\L_FV_DRAGON_INSTANCE_SERVER.lua", []() {return new FvDragonInstanceServer();}}, + {"scripts\\02_server\\Enemy\\FV\\L_FV_DRAGON_RONIN.lua", []() {return new DragonRonin();}}, {"scripts\\02_server\\Enemy\\FV\\L_FV_MAELSTROM_DRAGON.lua", []() {return new FvMaelstromDragon();}}, + {"scripts\\02_server\\Enemy\\General\\L_COUNTDOWN_DESTROY_AI.lua", []() {return new CountdownDestroyAI();}}, {"scripts\\ai\\FV\\L_FV_DRAGON_SMASHING_GOLEM_QB.lua", []() {return new FvDragonSmashingGolemQb();}}, {"scripts\\02_server\\Enemy\\General\\L_TREASURE_CHEST_DRAGON_SERVER.lua", []() {return new TreasureChestDragonServer();}}, {"scripts\\ai\\GENERAL\\L_INSTANCE_EXIT_TRANSFER_PLAYER_TO_LAST_NON_INSTANCE.lua", []() {return new InstanceExitTransferPlayerToLastNonInstance();}}, diff --git a/dScripts/ai/FV/CMakeLists.txt b/dScripts/ai/FV/CMakeLists.txt index 535a02a6..eaa493c6 100644 --- a/dScripts/ai/FV/CMakeLists.txt +++ b/dScripts/ai/FV/CMakeLists.txt @@ -18,7 +18,13 @@ set(DSCRIPTS_SOURCES_AI_FV "FvMaelstromGeyser.cpp" "TriggerGas.cpp") +add_subdirectory(Dragon_Instance) + +foreach(file ${DSCRIPTS_SOURCES_AI_FV_DRAGON_INSTANCE}) + set(DSCRIPTS_SOURCES_AI_FV ${DSCRIPTS_SOURCES_AI_FV} "Dragon_Instance/${file}") +endforeach() + add_library(dScriptsAiFV OBJECT ${DSCRIPTS_SOURCES_AI_FV}) -target_include_directories(dScriptsAiFV PUBLIC ".") +target_include_directories(dScriptsAiFV PUBLIC "." "Dragon_Instance") target_precompile_headers(dScriptsAiFV REUSE_FROM dScriptsBase) diff --git a/dScripts/ai/FV/Dragon_Instance/CMakeLists.txt b/dScripts/ai/FV/Dragon_Instance/CMakeLists.txt new file mode 100644 index 00000000..501c77cc --- /dev/null +++ b/dScripts/ai/FV/Dragon_Instance/CMakeLists.txt @@ -0,0 +1,3 @@ +set(DSCRIPTS_SOURCES_AI_FV_DRAGON_INSTANCE + "FvDragonInstanceServer.cpp" + PARENT_SCOPE) diff --git a/dScripts/ai/FV/Dragon_Instance/FvDragonInstanceServer.cpp b/dScripts/ai/FV/Dragon_Instance/FvDragonInstanceServer.cpp new file mode 100644 index 00000000..3e63d6b0 --- /dev/null +++ b/dScripts/ai/FV/Dragon_Instance/FvDragonInstanceServer.cpp @@ -0,0 +1,14 @@ +#include "FvDragonInstanceServer.h" + +#include "Entity.h" +#include "DestroyableComponent.h" + +void FvDragonInstanceServer::OnPlayerLoaded(Entity* self, Entity* player) { + auto* const destComp = player->GetComponent(); + if (destComp) { + destComp->SetHealth(destComp->GetMaxHealth()); + destComp->SetArmor(destComp->GetMaxArmor()); + destComp->SetImagination(destComp->GetMaxImagination()); + Game::entityManager->SerializeEntity(player); + } +} diff --git a/dScripts/ai/FV/Dragon_Instance/FvDragonInstanceServer.h b/dScripts/ai/FV/Dragon_Instance/FvDragonInstanceServer.h new file mode 100644 index 00000000..5dffcae4 --- /dev/null +++ b/dScripts/ai/FV/Dragon_Instance/FvDragonInstanceServer.h @@ -0,0 +1,7 @@ +#pragma once +#include "CppScripts.h" + +class FvDragonInstanceServer : public CppScripts::Script { +public: + void OnPlayerLoaded(Entity* self, Entity* player) override; +};