From 30479a0ef029a9e6fd23fda8ada20f29a05cc400 Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Sun, 21 Jun 2026 20:57:09 -0700 Subject: [PATCH] feat: implement ronin script --- dGame/dComponents/BaseCombatAIComponent.cpp | 4 ++ dGame/dComponents/ScriptComponent.cpp | 2 +- dGame/dComponents/ScriptComponent.h | 18 ++++++- dGame/dGameMessages/GameMessages.h | 8 +++ dScripts/02_server/Enemy/FV/CMakeLists.txt | 1 + dScripts/02_server/Enemy/FV/DragonRonin.cpp | 6 +++ dScripts/02_server/Enemy/FV/DragonRonin.h | 7 +++ .../02_server/Enemy/General/CMakeLists.txt | 1 + .../Enemy/General/CountdownDestroyAI.cpp | 50 +++++++++++++++++++ .../Enemy/General/CountdownDestroyAI.h | 13 +++++ dScripts/CppScripts.cpp | 4 ++ 11 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 dScripts/02_server/Enemy/FV/DragonRonin.cpp create mode 100644 dScripts/02_server/Enemy/FV/DragonRonin.h create mode 100644 dScripts/02_server/Enemy/General/CountdownDestroyAI.cpp create mode 100644 dScripts/02_server/Enemy/General/CountdownDestroyAI.h 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..9af78440 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,20 @@ 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&)) { + const auto boundMsg = std::bind(scriptHandler, scriptThis, std::placeholders::_1, std::placeholders::_2); + const auto castWrapper = [this, boundMsg](GameMessages::GameMsg& msg) { + return boundMsg(*m_Parent, static_cast(msg)); + }; + DerivedMsgType msg; + m_Parent->RegisterMsg(msg.msgId, castWrapper); + } + private: /** @@ -63,6 +77,8 @@ private: bool m_Client; std::string m_ScriptName; + + std::map> m_MsgHandlers; }; #endif // SCRIPTCOMPONENT_H diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 7a34de6c..0497613d 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, @@ -971,5 +972,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..266b494f --- /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); + 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..f0284c51 --- /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) { + 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 9ebe8bde..2e0d7a83 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -137,7 +137,9 @@ #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" @@ -492,7 +494,9 @@ namespace { {"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();}},