#include "BossSpiderQueenEnemyServer.h" #include "GeneralUtils.h" #include "MissionComponent.h" #include "EntityManager.h" #include "Entity.h" #include "dZoneManager.h" #include "DestroyableComponent.h" #include "ControllablePhysicsComponent.h" #include "BaseCombatAIComponent.h" #include "GameMessages.h" #include "EffectsMessages.h" #include "ObjectMessages.h" #include "SkillComponent.h" #include "eReplicaComponentType.h" #include "RenderComponent.h" #include "PlayerManager.h" #include "eStateChangeType.h" #include #include //---------------------------------------------------------------- //--On Startup, process necessary AI events //---------------------------------------------------------------- void BossSpiderQueenEnemyServer::OnStartup(Entity* self) { // Make immune to stuns //self:SetStunImmunity{ StateChangeType = "PUSH", bImmuneToStunAttack = true, bImmuneToStunMove = true, bImmuneToStunTurn = true, bImmuneToStunUseItem = true, bImmuneToStunEquip = true, bImmuneToStunInteract = true, bImmuneToStunJump = true } // Make immune to knockbacks and pulls //self:SetStatusImmunity{ StateChangeType = "PUSH", bImmuneToPullToPoint = true, bImmuneToKnockback = true, bImmuneToInterrupt = true } //Get our components: destroyable = static_cast(self->GetComponent(eReplicaComponentType::DESTROYABLE)); controllable = static_cast(self->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); combat = static_cast(self->GetComponent(eReplicaComponentType::BASE_COMBAT_AI)); if (!destroyable || !controllable) return; // Determine Spider Boss health transition thresholds int spiderBossHealth = destroyable->GetMaxHealth(); int transitionTickHealth = spiderBossHealth / 3; int Stage2HealthThreshold = spiderBossHealth - transitionTickHealth; int Stage3HealthThreshold = spiderBossHealth - (2 * transitionTickHealth); ThresholdTable = { Stage2HealthThreshold, Stage3HealthThreshold }; originRotation = controllable->GetRotation(); combat->SetStunImmune(true); m_CurrentBossStage = 1; ToggleAttacking(*self, false); self->SetProximityRadius(65.0f, "AggroRadius"); // Obtain faction and collision group to save for subsequent resets } void BossSpiderQueenEnemyServer::OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) { if (name != "AggroRadius" || !entering || !entering->IsPlayer()) return; auto playerCount = self->GetVar(u"player_count"); if (status == "ENTER") { if (playerCount == 0) { ToggleAttacking(*self, true); } playerCount++; } else if (status == "LEAVE") { playerCount--; if (playerCount == 0) { ToggleAttacking(*self, false); } } self->SetVar(u"player_count", playerCount); } void BossSpiderQueenEnemyServer::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) { if (args != "QueryZoneScript") return; // The zone script sets SpiderEggNetworkID, LandingTarget and ScreamEmitter on the boss auto* const zoneControl = Game::entityManager->GetZoneControlEntity(); if (zoneControl) zoneControl->OnFireEventServerSide(self, "RetrieveZoneData"); } void BossSpiderQueenEnemyServer::OnNotifyObject(Entity* self, Entity* sender, const std::string& name, int32_t param1, int32_t param2) { if (name != "SpiderlingDied" || !sender || sender->GetLOT() != SpiderlingID) return; // The boss's client script plays the mountain scream from the emitter it is given GameMessages::NotifyClientObject(self->GetObjectID(), u"EmitScream", 0, 0, self->GetVar(u"ScreamEmitter"), "").Send(UNASSIGNED_SYSTEM_ADDRESS); } void BossSpiderQueenEnemyServer::OnDie(Entity* self, Entity* killer) { if (Game::zoneManager->GetZoneID().GetMapID() == instanceZoneID && killer) { for (const auto& player : PlayerManager::GetAllPlayers()) { auto* missionComponent = player->GetComponent(); if (missionComponent == nullptr) return; missionComponent->CompleteMission(instanceMissionID); } } SpiderSkillManager(self, false); // There is suppose to be a 0.1 second delay here but that may be admitted? auto* controller = Game::entityManager->GetZoneControlEntity(); GameMessages::NotifyClientObject(self->GetObjectID(), u"SetColGroup", 10, 0, 0, "").Send(UNASSIGNED_SYSTEM_ADDRESS); self->SetPosition({ 10000, 0, 10000 }); Game::entityManager->SerializeEntity(self); ToggleAttacking(*self, false); controller->OnFireEventServerSide(self, "ClearProperty"); } void BossSpiderQueenEnemyServer::WithdrawSpider(Entity* self, const bool withdraw) { const auto withdrawn = self->GetBoolean(u"isWithdrawn"); if (withdrawn == withdraw) { return; } if (withdraw) { //Move spider away from battle zone // Disabled because we cant option the reset collition group right now GameMessages::NotifyClientObject(self->GetObjectID(), u"SetColGroup", 10, 0, 0, "").Send(UNASSIGNED_SYSTEM_ADDRESS); //First rotate for anim NiQuaternion rot = QuatUtils::IDENTITY; controllable->SetStatic(false); controllable->SetRotation(rot); controllable->SetStatic(true); controllable->SetDirtyPosition(true); rot = controllable->GetRotation(); Game::entityManager->SerializeEntity(self); auto* baseCombatAi = self->GetComponent(); baseCombatAi->SetDisabled(true); float animTime = PlayAnimAndReturnTime(self, spiderWithdrawAnim); float withdrawTime = animTime - 0.25f; combat->SetStunImmune(false); combat->Stun(withdrawTime + 6.0f); combat->SetStunImmune(true); destroyable->SetFaction(-1); destroyable->SetIsImmune(true); Game::entityManager->SerializeEntity(self); self->AddTimer("WithdrawComplete", withdrawTime + 1.0f); waitForIdle = true; } else { controllable->SetStatic(false); //Cancel all remaining timers for say idle anims: self->CancelAllTimers(); auto* baseCombatAi = self->GetComponent(); baseCombatAi->SetDisabled(false); // Move the Spider to its ground location // preparing its stage attacks, and removing invulnerability //destroyable->SetIsImmune(false); // Run the advance animation and prepare a timer for resuming AI float animTime = PlayAnimAndReturnTime(self, spiderAdvanceAnim); animTime += 1.f; float attackPause = animTime - 0.4f; destroyable->SetFaction(4); destroyable->SetIsImmune(false); //Advance stage m_CurrentBossStage++; //Reset the current wave death counter m_DeathCounter = 0; Game::entityManager->SerializeEntity(self); // Prepare a timer for post leap attack self->AddTimer("AdvanceAttack", attackPause); // Prepare a timer for post leap self->AddTimer("AdvanceComplete", animTime); } self->SetBoolean(u"isWithdrawn", withdraw); } void BossSpiderQueenEnemyServer::SpawnSpiderWave(Entity* self, int spiderCount) { // The Spider Queen Boss is withdrawing and requesting the spawn // of a hatchling wave // Clamp invalid Spiderling number requests to the maximum amount of eggs available if ((spiderCount > maxSpiderEggCnt) || (spiderCount < 0)) spiderCount = maxSpiderEggCnt; // Reset our wave manager reference variables hatchCounter = spiderCount; hatchList = {}; // Run the wave manager SpiderWaveManager(self); } void BossSpiderQueenEnemyServer::SpiderWaveManager(Entity* self) { auto SpiderEggNetworkID = self->GetI64(u"SpiderEggNetworkID"); std::vector spiderEggs{}; auto spooders = Game::entityManager->GetEntitiesInGroup("SpiderEggs"); for (auto spodder : spooders) { spiderEggs.push_back(spodder->GetObjectID()); } // Select a number of random spider eggs from the list equal to the // current number needed to complete the current wave if (!spiderEggs.empty()) { for (int i = 0; i < hatchCounter; i++) { // Select a random spider egg auto randomEggLoc = GeneralUtils::GenerateRandomNumber(0, spiderEggs.size() - 1); auto randomEgg = spiderEggs[randomEggLoc]; //Just a quick check to try and prevent dupes: for (auto en : hatchList) { if (en == randomEgg) { randomEggLoc++; randomEgg = spiderEggs.at(randomEggLoc % spiderEggs.size()); } } if (randomEgg) { auto* eggEntity = Game::entityManager->GetEntity(randomEgg); if (eggEntity == nullptr) { continue; } // Prep the selected spider egg eggEntity->OnFireEventServerSide(self, "prepEgg"); // Add the prepped egg to our hatchList hatchList.push_back(eggEntity->GetObjectID()); // Decrement the hatchCounter hatchCounter = hatchCounter - 1; } // Remove it from our spider egg list spiderEggs[randomEggLoc] = LWOOBJID_EMPTY; if (spiderEggs.size() <= 0 || (hatchCounter <= 0)) { break; } } } if (hatchCounter > 0) { // We still have more eggs to hatch, poll the SpiderWaveManager again self->AddTimer("PollSpiderWaveManager", 1.0f); } else { // We have successfully readied a full wave // initiate hatching! for (auto egg : hatchList) { auto* eggEntity = Game::entityManager->GetEntity(egg); if (eggEntity == nullptr) { continue; } eggEntity->OnFireEventServerSide(self, "hatchEgg"); auto time = PlayAnimAndReturnTime(self, spiderWithdrawIdle); combat->SetStunImmune(false); combat->Stun(time += 6.0f); combat->SetStunImmune(true); self->AddTimer("checkForSpiders", 6.0f); } hatchList.clear(); } } void BossSpiderQueenEnemyServer::ToggleForSpecial(Entity* self, const bool state) { // Kept apart from "stoppedFlag", which is the no-players-around attack stop (ToggleAttacking) self->SetBoolean(u"isSpecialAttacking", state); UpdateSpecialStun(self); } void BossSpiderQueenEnemyServer::SetRainOfFireStun(Entity* self, const bool state) { // Live: a separate "can't attack" stun from the start of a rain of fire until its last impact self->SetBoolean(u"rainOfFireStun", state); UpdateSpecialStun(self); } void BossSpiderQueenEnemyServer::UpdateSpecialStun(Entity* self) { combat->SetDisabled(self->GetBoolean(u"isSpecialAttacking") || self->GetBoolean(u"rainOfFireStun")); } void BossSpiderQueenEnemyServer::SpiderSkillManager(Entity* self, const bool active) { if (!active) { self->CancelTimer("PollSpiderSkillManager"); return; } // Within smashSkillLength of the melee smash: run the special once the smash is over if (self->GetBoolean(u"bSpecialLock")) { self->SetBoolean(u"bSpecialQueued", true); return; } if (self->GetBoolean(u"isWithdrawn")) return; // Stage 2 only fires the rapid fire shooter, stage 3 only the rain of fire if (m_CurrentBossStage == 2) { RunRapidFireShooter(self); } else if (m_CurrentBossStage == 3) { RunRainOfFire(self); } } void BossSpiderQueenEnemyServer::OnSkillCast(Entity* self, const uint32_t skillID) { if (skillID != bossSmashSkill) return; // The melee smash locks the specials for as long as it lasts self->SetBoolean(u"bSpecialLock", true); self->CancelTimer("UnlockSpecials"); self->AddTimer("UnlockSpecials", smashSkillLength); } void BossSpiderQueenEnemyServer::RunRainOfFire(Entity* self) { ToggleForSpecial(self, true); SetRainOfFireStun(self, true); impactList.clear(); auto index = 0u; for (const auto& rofGroup : ROFTargetGroupIDTable) { const auto spawners = Game::zoneManager->GetSpawnersInGroup(rofGroup); std::vector spawned; for (auto* spawner : spawners) { for (const auto* node : spawner->m_Info.nodes) { spawned.insert(spawned.end(), node->entities.begin(), node->entities.end()); } } if (index == 0) { impactList.insert(impactList.end(), spawned.begin(), spawned.end()); } else if (!spawned.empty()) { const auto randomIndex = GeneralUtils::GenerateRandomNumber(0, spawned.size() - 1); impactList.push_back(spawned[randomIndex]); } index++; } const auto animTime = PlayAnimAndReturnTime(self, spiderROFAnim); self->AddTimer("StartROF", animTime); } void BossSpiderQueenEnemyServer::RainOfFireManager(Entity* self) { if (!impactList.empty()) { auto* entity = Game::entityManager->GetEntity(impactList[0]); impactList.erase(impactList.begin()); if (entity == nullptr) { LOG("Failed to find impact!"); return; } auto* skillComponent = entity->GetComponent(); if (skillComponent == nullptr) { LOG("Failed to find impact skill component!"); return; } skillComponent->CalculateBehavior(1376, 32168, LWOOBJID_EMPTY, true); self->AddTimer("PollROFManager", 0.5f); return; } SetRainOfFireStun(self, false); self->AddTimer("PollSpiderSkillManager", static_cast(GeneralUtils::GenerateRandomNumber(s2DelayMin, s2DelayMax))); } void BossSpiderQueenEnemyServer::RapidFireShooterManager(Entity* self) { if (attackTargetTable.empty()) { const auto animationTime = PlayAnimAndReturnTime(self, spiderJeerAnim); self->AddTimer("RFSTauntComplete", animationTime); return; } const auto target = attackTargetTable[0]; auto* skillComponent = self->GetComponent(); skillComponent->CalculateBehavior(1394, 32612, target, true); attackTargetTable.erase(attackTargetTable.begin()); self->AddTimer("PollRFSManager", 0.3f); } void BossSpiderQueenEnemyServer::RunRapidFireShooter(Entity* self) { // The sweep starts around a random player, from the zone volume that player last entered const auto& players = PlayerManager::GetAllPlayers(); if (players.empty()) { self->AddTimer("PollSpiderSkillManager", static_cast(GeneralUtils::GenerateRandomNumber(s1DelayMin, s1DelayMax))); return; } ToggleForSpecial(self, true); const auto* const attackFocus = players[GeneralUtils::GenerateRandomNumber(0, players.size() - 1)]; const auto inZone = inZoneTable.find(attackFocus->GetObjectID()); const auto& primaryZone = inZone != inZoneTable.end() ? inZone->second : defaultFacingZone; // Clockwise or counter-clockwise at random const bool clockwise = GeneralUtils::GenerateRandomNumber(1, 2) == 1; attackTargetTable = BuildRapidFireTargets(primaryZone, clockwise, [](const std::string& group) { std::vector targets; for (auto* const target : Game::entityManager->GetEntitiesInGroup(group)) { targets.push_back({ target->GetObjectID(), target->GetVar(u"CWOrder"), target->GetVar(u"CWOrder2") }); } return targets; }); // Turn towards the fourth target, then shoot every target in turn auto* skillComponent = self->GetComponent(); skillComponent->CalculateBehavior(1480, 36652, attackTargetTable.size() >= 4 ? attackTargetTable[3] : LWOOBJID_EMPTY, true); RapidFireShooterManager(self); PlayAnimAndReturnTime(self, clockwise ? spiderShootRght : spiderShootLeft); } std::vector BossSpiderQueenEnemyServer::BuildRapidFireTargets(const std::string& primaryZone, const bool clockwise, const std::function(const std::string& group)>& groupTargets) { std::vector attackTargets; const auto zoneGroups = rapidFireTargetTable.find(primaryZone); if (zoneGroups == rapidFireTargetTable.end()) return attackTargets; const auto& groups = zoneGroups->second; const auto& primaryGroup = clockwise ? groups[0] : groups[2]; const auto& secondaryGroup = groups[1]; const auto& tertiaryGroup = clockwise ? groups[2] : groups[0]; // Zones 8 and 1 meet where CWOrder starts again; sweeps over that edge sort by CWOrder2 const bool crossesStart = std::ranges::any_of(groups, [](const std::string& group) { return group == "Zone1Targets" || group == "Zone8Targets"; }); for (const auto* const group : { &primaryGroup, &secondaryGroup, &tertiaryGroup }) { auto targets = groupTargets(*group); std::ranges::stable_sort(targets, [crossesStart, clockwise](const RapidFireTarget& a, const RapidFireTarget& b) { const auto orderA = crossesStart ? a.cwOrder2 : a.cwOrder; const auto orderB = crossesStart ? b.cwOrder2 : b.cwOrder; return clockwise ? orderA < orderB : orderA > orderB; }); // The middle group leaves out its first and last target const bool isSecondary = group == &secondaryGroup; for (size_t i = 0; i < targets.size(); i++) { if (isSecondary && (i == 0 || i == targets.size() - 1)) continue; attackTargets.push_back(targets[i].id); } } return attackTargets; } void BossSpiderQueenEnemyServer::OnZoneVolumeEntered(Entity* self, Entity* player, const std::string& volumeGroup) { if (!player || !player->IsPlayer()) return; // The aggro volume is not a quadrant (the boss's aggro uses her proximity radius) if (volumeGroup == "AggroVol") return; // A teleported player faces the default zone inZoneTable[player->GetObjectID()] = volumeGroup == "TeleVol" ? defaultFacingZone : volumeGroup; } void BossSpiderQueenEnemyServer::OnTimerDone(Entity* self, const std::string timerName) { if (timerName == "PollSpiderWaveManager") { //Call the manager again to attempt to finish prepping a Spiderling wave //Run the wave manager SpiderWaveManager(self); } else if (timerName == "disableWaitForIdle") { waitForIdle = false; } else if (timerName == "checkForSpiders") { //Don't do anything if we ain't withdrawn: const auto withdrawn = self->GetBoolean(u"isWithdrawn"); if (!withdrawn) return; NiQuaternion rot = QuatUtils::IDENTITY; //First rotate for anim controllable->SetStatic(false); controllable->SetRotation(rot); controllable->SetStatic(true); Game::entityManager->SerializeEntity(self); //Play the Spider Boss' mountain idle anim auto time = PlayAnimAndReturnTime(self, spiderWithdrawIdle); combat->SetStunImmune(false); combat->Stun(time); combat->SetStunImmune(true); rot = controllable->GetRotation(); //If there are still baby spiders, don't do anyhting either const auto spiders = Game::entityManager->GetEntitiesInGroup("BabySpider"); if (spiders.size() > 0) self->AddTimer("checkForSpiders", time); else WithdrawSpider(self, false); } else if (timerName == "PollROFManager") { //Call the manager again to attempt to initiate an impact on another random location //Run the ROF Manager RainOfFireManager(self); } else if (timerName == "PollRFSManager") { //Call the manager again to attempt to initiate a rapid fire shot at the next sequential target //Run the ROF Manager RapidFireShooterManager(self); } else if (timerName == "StartROF") { //Re-enable Spider Boss ToggleForSpecial(self, false); RainOfFireManager(self); } else if (timerName == "PollSpiderSkillManager") { //Call the skill manager again to attempt to run the current Spider Boss //stage's special attack again SpiderSkillManager(self, true); } else if (timerName == "RFSTauntComplete") { //Set a timer based on a random cooldown to pulse the SpiderSkillManager again self->AddTimer("PollSpiderSkillManager", static_cast(GeneralUtils::GenerateRandomNumber(s1DelayMin, s1DelayMax))); //Re-enable Spider Boss ToggleForSpecial(self, false); } else if (timerName == "WithdrawComplete") { //Play the Spider Boss' mountain idle anim PlayAnimAndReturnTime(self, spiderWithdrawIdle); //The Spider Boss has retreated, hatch a wave! int currentStage = m_CurrentBossStage; //Prepare a Spiderling wave and initiate egg hatch events //self->SetVar(u"SpiderWaveCount", ) hatchCounter = 2; if (currentStage > 1) hatchCounter++; SpawnSpiderWave(self, spiderWaveCntTable[currentStage - 1]); } else if (timerName == "AdvanceAttack") { //Fire the melee smash skill to throw players back /*local landingTarget = self:GetVar("LandingTarget") or false if((landingTarget) and (landingTarget:Exists())) { local advSmashFlag = landingTarget:CastSkill{skillID = bossLandingSkill} landingTarget:PlayEmbeddedEffectOnAllClientsNearObject{radius = 100, fromObjectID = landingTarget, effectName = "camshake-bridge"} }*/ // The landing target casts the landing skill and shakes the camera, not the boss auto* const landingEntity = Game::entityManager->GetEntity(self->GetVar(u"LandingTarget")); if (landingEntity) { auto* const landingSkill = landingEntity->GetComponent(); if (landingSkill) landingSkill->CalculateBehavior(bossLandingSkill, 37739, LWOOBJID_EMPTY, true); GameMessages::PlayEmbeddedEffectOnAllClientsNearObject embeddedEffect; embeddedEffect.target = landingEntity->GetObjectID(); embeddedEffect.effectName = u"camshake-bridge"; embeddedEffect.fromObjectID = landingEntity->GetObjectID(); embeddedEffect.radius = 100.0f; embeddedEffect.Send(UNASSIGNED_SYSTEM_ADDRESS); } } else if (timerName == "AdvanceComplete") { GameMessages::NotifyClientObject(self->GetObjectID(), u"SetColGroup", 11, 0, 0, "").Send(UNASSIGNED_SYSTEM_ADDRESS); //Wind up, telegraphing next round float animTime = PlayAnimAndReturnTime(self, spiderJeerAnim); self->AddTimer("AdvanceTauntComplete", animTime); } else if (timerName == "AdvanceTauntComplete") { //Declare a default special Spider Boss skill cooldown int spiderCooldownDelay = 10; if (m_CurrentBossStage == 2) { spiderCooldownDelay = GeneralUtils::GenerateRandomNumber(s1DelayMin, s1DelayMax); } else if (m_CurrentBossStage == 3) { spiderCooldownDelay = GeneralUtils::GenerateRandomNumber(s2DelayMin, s2DelayMax); } //Set a timer based on our random cooldown determination //to pulse the SpiderSkillManager self->AddTimer("PollSpiderSkillManager", spiderCooldownDelay); //Remove current status immunity /*self:SetStatusImmunity{ StateChangeType = "POP", bImmuneToSpeed = true, bImmuneToBasicAttack = true, bImmuneToDOT = true} self:SetStunned{StateChangeType = "POP", bCantMove = true, bCantJump = true, bCantTurn = true, bCantAttack = true, bCantUseItem = true, bCantEquip = true, bCantInteract = true, bIgnoreImmunity = true}*/ destroyable->SetIsImmune(false); destroyable->SetFaction(4); Game::entityManager->SerializeEntity(self); } else if (timerName == "Clear") { Game::entityManager->FireEventServerSide(self, "ClearProperty"); self->CancelAllTimers(); } else if (timerName == "UnlockSpecials") { //We no longer need to lock specials self->SetBoolean(u"bSpecialLock", false); //Did we queue a spcial attack? if (self->GetBoolean(u"bSpecialQueued")) { self->SetBoolean(u"bSpecialQueued", false); SpiderSkillManager(self, true); } } } void BossSpiderQueenEnemyServer::OnHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) { if (m_CurrentBossStage > ThresholdTable.size()) { return; } int currentThreshold = ThresholdTable[m_CurrentBossStage - 1]; if (destroyable->GetHealth() <= currentThreshold) { auto isWithdrawn = self->GetBoolean(u"isWithdrawn"); if (!isWithdrawn) { self->CancelAllTimers(); // Stop whatever special was running attackTargetTable.clear(); impactList.clear(); self->SetBoolean(u"isSpecialAttacking", false); self->SetBoolean(u"rainOfFireStun", false); self->SetBoolean(u"bSpecialLock", false); self->SetBoolean(u"bSpecialQueued", false); WithdrawSpider(self, true); } } } void BossSpiderQueenEnemyServer::OnUpdate(Entity* self) { auto isWithdrawn = self->GetBoolean(u"isWithdrawn"); if (!isWithdrawn) return; if (controllable->GetRotation() == QuatUtils::IDENTITY) { return; } controllable->SetStatic(false); controllable->SetRotation(QuatUtils::IDENTITY); controllable->SetStatic(true); Game::entityManager->SerializeEntity(self); } //---------------------------------------------- //--Utility function capable of playing a priority //-- animation on a targetand returning either the //-- anim time, or a desired default //---------------------------------------------- float BossSpiderQueenEnemyServer::PlayAnimAndReturnTime(Entity* self, const std::u16string& animID) { // Get the anim time float animTimer = RenderComponent::GetAnimationTime(self, animID); // If we have an animation play it if (animTimer > 0) { animTimer = RenderComponent::PlayAnimation(self, animID); } // If the anim time is less than the the default time use default if (animTimer < defaultAnimPause) { animTimer = defaultAnimPause; } return animTimer; } void BossSpiderQueenEnemyServer::ToggleAttacking(Entity& self, bool on) { const auto stoppedFlag = self.GetVarAs(u"stoppedFlag"); if (!on) { if (stoppedFlag) return; self.SetVar(u"stoppedFlag", true); combat->Stun(100000.0f, true); // forcibly stun so we stop attacking people trying to put on armor } else { if (!stoppedFlag) return; self.SetVar(u"stoppedFlag", false); combat->Stun(0.0f, true); // forcibly turn off the stun we put on above } }