diff --git a/dScripts/02_server/Objects/CMakeLists.txt b/dScripts/02_server/Objects/CMakeLists.txt index 5340e50cc..4d198c5d8 100644 --- a/dScripts/02_server/Objects/CMakeLists.txt +++ b/dScripts/02_server/Objects/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(Hatchlings) set(DSCRIPTS_SOURCES_02_SERVER_OBJECTS "AgSurvivalBuffStation.cpp" "ImmovableTurret.cpp" + "NjSpinnerServer.cpp" "StinkyFishTarget.cpp") foreach(file ${DSCRIPTS_SOURCES_02_SERVER_OBJECTS_HATCHLINGS}) diff --git a/dScripts/02_server/Objects/NjSpinnerServer.cpp b/dScripts/02_server/Objects/NjSpinnerServer.cpp new file mode 100644 index 000000000..97ed7d840 --- /dev/null +++ b/dScripts/02_server/Objects/NjSpinnerServer.cpp @@ -0,0 +1,412 @@ +#include "NjSpinnerServer.h" + +#include "BuffComponent.h" +#include "EffectsMessages.h" +#include "Entity.h" +#include "EntityManager.h" +#include "GameMessages.h" +#include "MovingPlatformComponent.h" +#include "Preconditions.h" +#include "ProximityMonitorComponent.h" +#include "RenderComponent.h" +#include "SkillComponent.h" +#include "Spawner.h" +#include "dZoneManager.h" + +namespace { + // L_NJ_SPINNER_SERVER's defaults when the level config sets nothing + constexpr float DefaultSpinDistance = 3.5f; + constexpr float DefaultDamageRadius = 8.0f; + constexpr float DefaultPulseTime = 1.0f; + constexpr float DefaultResetTime = 0.0f; + constexpr float DamageStartDelay = 1.0f; // after reaching the active position, before the damage proximity starts + const std::u16string DefaultSpawnFXType = u"spawner_activate"; + + // The player's animations, and the spinner's + const std::string StartAnim = "spinjitzu-staff-windup"; + const std::string RunAnim = "spinjitzu-staff-loop"; + const std::string EndAnim = "spinjitzu-staff-end"; + const std::string DownAnimModifier = "-down"; + const std::u16string UpAnim = u"up"; + const std::u16string DownAnim = u"down"; + const std::u16string IdleDownAnim = u"idle"; + const std::u16string IdleUpAnim = u"idle-up"; + + const std::string AudioMovementBegin = "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}"; + const std::string AudioMovementEnd = "{40e86d71-084c-4149-884e-ab9b45b694dc}"; + const std::string AudioMovement = "{b91d2f01-1998-4ca3-bb5c-c25cf36c7a24}"; + const std::string AudioSpinning = "{8dcf670f-9fe9-4f14-b5ba-0a8e0943c2f6}"; + + constexpr uint32_t BladeSkill = 971; + constexpr uint32_t ArrowSkill = 972; + + std::string Text(Entity* self, const std::u16string& name) { return self->GetVarAsString(name); } + float Number(Entity* self, const std::u16string& name, const float fallback) { + return self->HasVar(name) ? self->GetVarAs(name) : fallback; + } + bool Flag(Entity* self, const std::u16string& name) { return self->HasVar(name) && self->GetVar(name); } + int32_t StoppedNode(Entity* self) { return self->GetVar(u"stoppedNode"); } +} + +NjSpinnerServer::Element NjSpinnerServer::GetElement(Entity* self) { + const auto element = Text(self, u"element_type"); + if (element == "fire") return { "spinner_interaction", 144 }; + if (element == "earth") return { "spinner_interaction", 145 }; + if (element == "ice") return { "spinner_interaction", 146 }; + if (element == "lightning") return { "spinner_interaction", 147 }; + if (element == "collision") return { "green-switch", 143 }; + if (element == "collision_off") return { "red-switch", 143 }; + return { "spinner_interaction", 143 }; // imagination +} + +std::string NjSpinnerServer::Audio(Entity* self, const std::u16string& var, const std::string& variantDefault, const std::string& baseDefault) const { + const auto set = Text(self, var); + if (!set.empty()) return set; + return variantDefault.empty() ? baseDefault : variantDefault; +} + +void NjSpinnerServer::OnStartup(Entity* self) { + // The variant scripts' defaults + if (!self->HasVar(u"type") && !m_Defaults.type.empty()) self->SetVar(u"type", m_Defaults.type); + if (!self->HasVar(u"element_type") && !m_Defaults.elementType.empty()) self->SetVar(u"element_type", m_Defaults.elementType); + + if (!self->HasVar(u"attached_path") && !Flag(self, u"platformIsSimpleMover")) { + LOG("Spinner %llu (LOT %i) has no attached_path", self->GetObjectID(), self->GetLOT()); + return; + } + + self->SetProximityRadius(Number(self, u"spin_distance", DefaultSpinDistance), "spin_distance"); + + if (auto* const platform = self->GetComponent()) platform->StopPathing(); + + const auto startNode = self->GetVarAs(u"attached_path_start"); + self->SetVar(u"stoppedNode", startNode); + + if (Text(self, u"element_type") != "collision" && !Flag(self, u"static") && !Flag(self, u"noninteract")) { + self->SetNetworkVar(u"bInteractive", true); + SetCapFX(self, true); + } else if (!self->GetNetworkVar(u"bInteractive") || Flag(self, u"noninteract")) { + self->SetNetworkVar(u"bInteractive", false); + SetCapFX(self, false); + } + + self->SetNetworkVar(u"element_precondition_ID", GetElement(self).precondition); + + if (startNode == 1) { + ToggleActive(self, false); + self->SetNetworkVar(u"current_anim", IdleUpAnim); + ToggleDamageProx(self, true); + ToggleSpawnerNetwork(self, true); + self->SetVar(u"movingUp", false); + } else { + self->SetVar(u"movingUp", true); + } +} + +void NjSpinnerServer::SetCapFX(Entity* self, const bool on) { + if (Flag(self, u"static")) return; + const auto cap = GetElement(self); + if (on) { + GameMessages::PlayFXEffect(self->GetObjectID(), -1, GeneralUtils::ASCIIToUTF16(cap.capFX), "element_" + cap.capFX).Send(UNASSIGNED_SYSTEM_ADDRESS); + self->SetVar(u"bCapShown", true); + } else { + GameMessages::StopFXEffect(self->GetObjectID(), true, "element_" + cap.capFX).Send(UNASSIGNED_SYSTEM_ADDRESS); + self->SetVar(u"bCapShown", false); + } +} + +void NjSpinnerServer::PlayAnim(Entity* self, Entity* player, const std::string& animation) { + const auto time = RenderComponent::GetAnimationTime(player, animation); + if (time <= 0.0f) return; + const auto name = self->GetVar(u"movingUp") ? animation : animation + DownAnimModifier; + RenderComponent::PlayAnimation(player, name, 2.0f); + self->AddTimer("AnimDone_" + animation, time); +} + +void NjSpinnerServer::OnUse(Entity* self, Entity* user) { + // The use requirements: not in use, the object's CheckPrecondition, the element's precondition + if (self->GetNetworkVar(u"bIsInUse")) return; + const auto preconditions = Text(self, u"CheckPrecondition"); + if (!preconditions.empty() && !PreconditionExpression(preconditions).Check(user)) return; + const auto element = GetElement(self).precondition; + if (element != -1 && !Preconditions::Check(user, element)) return; + + if (!self->GetVar(u"bCapShown")) return; + + self->SetNetworkVar(u"bFreezePlayer_" + GeneralUtils::to_u16string(user->GetObjectID()), true); + self->SetNetworkVar(u"bIsInUse", true); + GameMessages::PlayFXEffect(user->GetObjectID(), 5499, u"on-anim", "tornado").Send(UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::PlayFXEffect(user->GetObjectID(), 5502, u"on-anim", "staff").Send(UNASSIGNED_SYSTEM_ADDRESS); + + self->SetVar(u"userID", user->GetObjectID()); + PlayAnim(self, user, StartAnim); +} + +void NjSpinnerServer::SetActiveState(Entity* self, const bool active, const bool playerTriggered) { + self->SetVar(u"bActive", active); + if (playerTriggered) PlayCinematic(self, active); + + if (active) { + // An arrow spinner shoots every skill_pulse_time while it is up + if (Text(self, u"type") == "arrow") self->AddTimer("Pulse_Skill", Number(self, u"skill_pulse_time", DefaultPulseTime)); + self->SetVar(u"movingUp", true); + self->SetNetworkVar(u"current_anim", UpAnim); + } else { + self->SetVar(u"movingUp", false); + self->SetNetworkVar(u"current_anim", DownAnim); + } +} + +void NjSpinnerServer::ToggleActive(Entity* self, const bool playerTriggered) { + SetActiveState(self, !self->GetVar(u"bActive"), playerTriggered); +} + +void NjSpinnerServer::ToggleSpawnerNetwork(Entity* self, const bool active) { + std::vector networks; + for (int32_t i = 1; self->HasVar(u"spawnNw" + GeneralUtils::to_u16string(i)); i++) networks.push_back(Text(self, u"spawnNw" + GeneralUtils::to_u16string(i))); + if (networks.empty()) return; + + if (active && !Flag(self, u"NoSpawnerFX")) { + const auto fx = Text(self, u"spawnFXType"); + GameMessages::PlayFXEffect(self->GetObjectID(), -1, fx.empty() ? DefaultSpawnFXType : GeneralUtils::ASCIIToUTF16(fx), "spawnFX").Send(UNASSIGNED_SYSTEM_ADDRESS); + } + + for (const auto& network : networks) { + const auto spawners = Game::zoneManager->GetSpawnersByName(network); + if (spawners.empty()) { + LOG("Spinner %llu: spawner network %s doesn't exist", self->GetObjectID(), network.c_str()); + continue; + } + for (auto* const spawner : spawners) { + if (active) { + spawner->Activate(); + self->SetVar(u"TotalSpawnedAlive", self->GetVar(u"TotalSpawnedAlive") + spawner->m_Info.maxToSpawn); + // Lua's NotifySpawnerOfDeath request, once per network + const auto watched = u"watching_" + GeneralUtils::ASCIIToUTF16(network); + if (self->GetVar(watched)) continue; + self->SetVar(watched, true); + const auto selfId = self->GetObjectID(); + spawner->AddSpawnedEntityDieCallback([this, selfId, network]() { + if (auto* const spinner = Game::entityManager->GetEntity(selfId)) OnSpawnedDied(spinner, network); + }); + } else { + spawner->DestroyAllEntities(); + spawner->Deactivate(); + } + } + } +} + +// notifyNotifySpawnerOfDeath: when everything the networks spawned is dead, the spinner goes down +void NjSpinnerServer::OnSpawnedDied(Entity* self, const std::string& network) { + if (!self->HasVar(u"TotalSpawnedAlive")) return; + const auto alive = self->GetVar(u"TotalSpawnedAlive") - 1; + if (alive < 0) return; + self->SetVar(u"TotalSpawnedAlive", alive); + + for (auto* const spawner : Game::zoneManager->GetSpawnersByName(network)) { + if (spawner->GetAmountSpawned() == 0) { + spawner->Deactivate(); + spawner->Reset(); + } + } + if (alive > 0) return; + ActivateSpinner(self, false, false); +} + +void NjSpinnerServer::PlayCinematic(Entity* self, const bool on) { + auto* const player = Game::entityManager->GetEntity(self->GetVar(u"userID")); + if (!player) return; + const auto cinematic = self->GetVar(on ? u"on_cinematic" : u"off_cinematic"); + if (cinematic.empty()) return; + if (auto* const buffs = player->GetComponent()) buffs->RemoveBuff(60); + GameMessages::PlayCinematic message; + message.target = player->GetObjectID(); + message.pathName = cinematic; + message.Send(player->GetSystemAddress()); +} + +void NjSpinnerServer::CastSkill(Entity* self) { + const auto type = Text(self, u"type"); + if (type.empty() || type == "elevator" || !self->GetVar(u"bActive")) return; + auto* const skills = self->GetComponent(); + if (skills) skills->CastSkill(type == "arrow" ? ArrowSkill : BladeSkill); +} + +void NjSpinnerServer::ToggleSpinner(Entity* self, const bool playerTriggered) { + ActivateSpinner(self, StoppedNode(self) != 1, playerTriggered); +} + +void NjSpinnerServer::ActivateSpinner(Entity* self, const bool activate, const bool playerTriggered) { + const int32_t gotoNode = activate ? 1 : 0; + if (gotoNode == StoppedNode(self)) return; + + self->CancelAllTimers(); + self->SetNetworkVar(u"bIsInUse", true); + self->SetVar(u"bTraveling", true); + + if (playerTriggered) { + const auto resetTime = Number(self, u"reset_time", DefaultResetTime); + if (Flag(self, u"once_only")) SetCapFX(self, false); + else if (resetTime > 0.0f) self->SetVar(u"startResetOnArrived", true); + else self->SetVar(u"resetOnArrived", true); + } else { + self->SetVar(u"resetOnArrived", true); + } + + if (Text(self, u"element_type") == "collision" && self->GetVar(u"bCapShown")) { + SetCapFX(self, false); + self->SetVar(u"element_type", u"collision_off"); + SetCapFX(self, true); + } + + if (auto* const platform = self->GetComponent()) platform->GotoWaypoint(gotoNode, true); + self->SetVar(u"stoppedNode", gotoNode); + + GameMessages::PlayNDAudioEmitter(self->GetObjectID(), Audio(self, u"audioMovementBeginGUID", m_Defaults.audioMovementBegin, AudioMovementBegin)).Send(UNASSIGNED_SYSTEM_ADDRESS); + self->SetNetworkVar(u"StopAudio", ""); + self->SetNetworkVar(u"PlayAudio", Audio(self, u"audioMovementGUID", m_Defaults.audioMovement, AudioMovement)); + + SetActiveState(self, activate, playerTriggered); +} + +void NjSpinnerServer::ToggleDamageProx(Entity* self, const bool on) { + // Only a blade spinner hurts through a proximity + if (Text(self, u"type") != "blade") return; + const auto audio = Audio(self, u"audioSpinningGUID", m_Defaults.audioSpinning, AudioSpinning); + if (on) { + self->SetProximityRadius(Number(self, u"damage_radius", DefaultDamageRadius), "damage"); + self->SetNetworkVar(u"StopAudio2", ""); + self->SetNetworkVar(u"PlayAudio2", audio); + } else { + if (auto* const proximity = self->GetComponent()) proximity->UnsetProximityRadius("damage"); + self->SetNetworkVar(u"PlayAudio2", ""); + self->SetNetworkVar(u"StopAudio2", audio); + } +} + +void NjSpinnerServer::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) { + // The level config names what each incoming event does (event1 -> 0:activate) + const auto eventType = Text(self, GeneralUtils::ASCIIToUTF16(args)); + if (eventType.empty()) return; + + const bool canInteract = Text(self, u"element_type") != "collision" && !Flag(self, u"static"); + if (eventType == "activate") ActivateSpinner(self, true, false); + else if (eventType == "deactivate") ActivateSpinner(self, false, false); + else if (eventType == "toggle_activate") ToggleSpinner(self, false); + else if (eventType == "interact") { + if (canInteract) self->SetNetworkVar(u"bInteractive", true); + SetCapFX(self, true); + } else if (eventType == "noninteract") { + if (canInteract) self->SetNetworkVar(u"bInteractive", false); + SetCapFX(self, false); + } else if (eventType == "toggle_interact") { + const bool interactive = !self->GetNetworkVar(u"bInteractive"); + if (canInteract) self->SetNetworkVar(u"bInteractive", interactive); + SetCapFX(self, interactive); + } + + if (auto* const player = Game::entityManager->GetEntity(self->GetVar(u"userID"))) PlayAnim(self, player, EndAnim); +} + +void NjSpinnerServer::SendEvent(Entity* self, const bool on) { + const auto name = Text(self, on ? u"name_activated_event" : u"name_deactivated_event"); + const auto group = Text(self, on ? u"group_activated_event" : u"group_deactivated_event"); + if (name.empty() || group.empty()) return; + for (auto* const object : Game::entityManager->GetEntitiesInGroup(group)) { + if (object) object->OnFireEventServerSide(self, name); + } +} + +void NjSpinnerServer::OnWaypointReached(Entity* self, uint32_t waypointIndex) { + self->SetVar(u"bTraveling", false); + + if (static_cast(waypointIndex) == StoppedNode(self) && self->GetNetworkVar(u"bIsInUse")) { + if (self->GetVar(u"resetOnArrived")) { + self->SetNetworkVar(u"bIsInUse", false); + self->SetVar(u"resetOnArrived", false); + if (Text(self, u"element_type") == "collision_off") { + SetCapFX(self, false); + self->SetVar(u"element_type", u"collision"); + } + SetCapFX(self, true); + } else if (self->GetVar(u"startResetOnArrived")) { + self->AddTimer("Reset", Number(self, u"reset_time", DefaultResetTime)); + } + } + + const bool active = self->GetVar(u"bActive"); + if (active) { + self->AddTimer("Start_Damage", DamageStartDelay); + ToggleSpawnerNetwork(self, true); + self->SetNetworkVar(u"current_anim", IdleUpAnim); + } else { + ToggleDamageProx(self, false); + ToggleSpawnerNetwork(self, false); + self->SetNetworkVar(u"current_anim", IdleDownAnim); + } + + const auto movement = Audio(self, u"audioMovementGUID", m_Defaults.audioMovement, AudioMovement); + GameMessages::PlayNDAudioEmitter(self->GetObjectID(), Audio(self, u"audioMovementEndGUID", m_Defaults.audioMovementEnd, AudioMovementEnd)).Send(UNASSIGNED_SYSTEM_ADDRESS); + self->SetNetworkVar(u"PlayAudio", ""); + self->SetNetworkVar(u"StopAudio", movement); + + if (auto* const player = Game::entityManager->GetEntity(self->GetVar(u"userID"))) PlayAnim(self, player, EndAnim); + + SendEvent(self, active); +} + +void NjSpinnerServer::OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) { + if (name == "damage" && self->GetVar(u"bActive")) { + if (status == "ENTER") { + if (Text(self, u"type") == "blade") { + CastSkill(self); + self->CancelTimer("Pulse_Skill"); + self->AddTimer("Pulse_Skill", 0.5f); + self->SetVar(u"pulseInterval", 0.5f); + } + } else if (status == "LEAVE") { + auto* const proximity = self->GetComponent(); + if (!proximity || proximity->GetProximityObjects("damage").empty()) self->CancelTimer("Pulse_Skill"); + } + } else if (name == "spin_distance") { + const auto userId = self->GetVar(u"userID"); + if (status == "LEAVE") { + if (userId != LWOOBJID_EMPTY && entering && entering->GetObjectID() == userId) { + PlayAnim(self, entering, EndAnim); + if (self->GetNetworkVar(u"bIsInUse") && !self->GetVar(u"bTraveling")) self->SetNetworkVar(u"bIsInUse", false); + } + } else if (status == "ENTER" && Text(self, u"element_type") == "collision" && !self->GetVar(u"bTraveling") && self->GetVar(u"bCapShown")) { + ToggleSpinner(self, true); + } + } +} + +void NjSpinnerServer::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == "Pulse_Skill") { + // An activity timer with an update interval: it keeps pulsing until stopped + if (self->GetVar(u"bActive") && !self->GetVar(u"bTraveling")) CastSkill(self); + const auto interval = self->HasVar(u"pulseInterval") ? self->GetVar(u"pulseInterval") : Number(self, u"skill_pulse_time", DefaultPulseTime); + self->AddTimer("Pulse_Skill", interval); + } else if (timerName == "Reset") { + ToggleSpinner(self, false); + } else if (timerName.starts_with("AnimDone_")) { + const auto animation = timerName.substr(9); + auto* const player = Game::entityManager->GetEntity(self->GetVar(u"userID")); + if (!player) return; + if (animation == StartAnim) { + ToggleSpinner(self, true); + RenderComponent::PlayAnimation(player, RunAnim, 2.0f); + } else if (animation == RunAnim) { + PlayAnim(self, player, EndAnim); + } else if (animation == EndAnim) { + self->SetNetworkVar(u"bFreezePlayer_" + GeneralUtils::to_u16string(player->GetObjectID()), false); + self->SetVar(u"userID", LWOOBJID_EMPTY); + GameMessages::StopFXEffect(player->GetObjectID(), true, "tornado").Send(UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::StopFXEffect(player->GetObjectID(), true, "staff").Send(UNASSIGNED_SYSTEM_ADDRESS); + self->SetVar(u"movingUp", !self->GetVar(u"movingUp")); + } + } else if (timerName == "Start_Damage") { + ToggleDamageProx(self, true); + } +} diff --git a/dScripts/02_server/Objects/NjSpinnerServer.h b/dScripts/02_server/Objects/NjSpinnerServer.h new file mode 100644 index 000000000..0a3a735a7 --- /dev/null +++ b/dScripts/02_server/Objects/NjSpinnerServer.h @@ -0,0 +1,59 @@ +#pragma once +#include "CppScripts.h" + +#include + +/** + * L_NJ_SPINNER_SERVER and its 40 L_NJ___SPINNER scripts: the Ninjago spinner traps. A spinjitzu user + * (the element's precondition) or an event moves the spinner (a simple mover, MovingPlatforms rows 1-40) to its other + * end: up is active (a blade spinner hurts players near it with skill 971, an arrow one shoots skill 972 every + * skill_pulse_time, a spawn one activates its spawner networks), down is inactive. The variant scripts only pick + * defaults for type, element_type and the audio. + * Not as live: the proximities are spheres (live: cylinders of a height), and use is checked when used rather than + * by the client's use requirements. + */ +class NjSpinnerServer : public CppScripts::Script { +public: + struct Defaults { + std::u16string type; + std::u16string elementType; + std::string audioMovementBegin; + std::string audioMovementEnd; + std::string audioMovement; + std::string audioSpinning; + }; + + NjSpinnerServer() = default; + explicit NjSpinnerServer(Defaults defaults) : m_Defaults(std::move(defaults)) {} + + void OnStartup(Entity* self) override; + void OnUse(Entity* self, Entity* user) override; + void OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) override; + void OnWaypointReached(Entity* self, uint32_t waypointIndex) override; + void OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) override; + void OnTimerDone(Entity* self, std::string timerName) override; + +private: + struct Element { + std::string capFX; + int32_t precondition; + }; + static Element GetElement(Entity* self); + + void SetCapFX(Entity* self, bool on); + void PlayAnim(Entity* self, Entity* player, const std::string& animation); + void SetActiveState(Entity* self, bool active, bool playerTriggered); + void ToggleActive(Entity* self, bool playerTriggered); + void ToggleSpawnerNetwork(Entity* self, bool active); + void OnSpawnedDied(Entity* self, const std::string& network); + void PlayCinematic(Entity* self, bool on); + void CastSkill(Entity* self); + void ToggleSpinner(Entity* self, bool playerTriggered); + void ActivateSpinner(Entity* self, bool activate, bool playerTriggered); + void ToggleDamageProx(Entity* self, bool on); + void SendEvent(Entity* self, bool on); + + std::string Audio(Entity* self, const std::u16string& var, const std::string& variantDefault, const std::string& baseDefault) const; + + Defaults m_Defaults; +}; diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 0bb06e807..c84075099 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -77,6 +77,7 @@ #include "NsJohnnyMissionServer.h" #include "StinkyFishTarget.h" #include "ImmovableTurret.h" +#include "NjSpinnerServer.h" #include "ZoneNsProperty.h" #include "ZoneNsMedProperty.h" #include "NsTokenConsoleServer.h" @@ -437,6 +438,48 @@ namespace { {"scripts\\ai\\NS\\L_NS_CONCERT_INSTRUMENT_QB.lua", []() {return new NsConcertInstrument();}}, {"scripts\\ai\\NS\\L_NS_JONNY_FLAG_MISSION_SERVER.lua", []() {return new NsJohnnyMissionServer();}}, {"scripts\\02_server\\Objects\\L_STINKY_FISH_TARGET.lua", []() {return new StinkyFishTarget();}}, + // L_NJ_SPINNER_SERVER and the spinner traps that set its defaults + {"scripts\\02_server\\Objects\\NJ\\L_NJ_SPINNER_SERVER.lua", []() { return new NjSpinnerServer(); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_EARTH_AXE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"earth", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{43d11dc0-a096-4595-8fbe-f95a4a0d951e}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_EARTH_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"earth", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{3b8ac663-b5da-4aff-8aaf-6ce2fe6ccbde}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_EARTH_ELEVATOR_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"earth", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_EARTH_HIGH_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"earth", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{b1bbe65e-330d-4ef6-a534-63e98dd199ec}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_EARTH_LOW_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"earth", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{3062c5b2-b35a-4935-863f-a8c170aa1444}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_EARTH_PROJECTILE_SPINNER.lua", []() { return new NjSpinnerServer({ u"arrow", u"earth", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_EARTH_SPAWN_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"earth", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_EARTH_SWITCH_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"collision", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_FIRE_AXE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"fire", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{43d11dc0-a096-4595-8fbe-f95a4a0d951e}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_FIRE_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"fire", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{3b8ac663-b5da-4aff-8aaf-6ce2fe6ccbde}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_FIRE_ELEVATOR_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"fire", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_FIRE_HIGH_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"fire", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{b1bbe65e-330d-4ef6-a534-63e98dd199ec}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_FIRE_LOW_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"fire", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{3062c5b2-b35a-4935-863f-a8c170aa1444}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_FIRE_PROJECTILE_SPINNER.lua", []() { return new NjSpinnerServer({ u"arrow", u"fire", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_FIRE_SPAWN_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"fire", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_FIRE_SWITCH_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"collision", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_ICE_AXE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"ice", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{43d11dc0-a096-4595-8fbe-f95a4a0d951e}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_ICE_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"ice", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{3b8ac663-b5da-4aff-8aaf-6ce2fe6ccbde}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_ICE_ELEVATOR_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"ice", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_ICE_HIGH_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"ice", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{b1bbe65e-330d-4ef6-a534-63e98dd199ec}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_ICE_LOW_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"ice", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{3062c5b2-b35a-4935-863f-a8c170aa1444}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_ICE_PROJECTILE_SPINNER.lua", []() { return new NjSpinnerServer({ u"arrow", u"ice", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_ICE_SPAWN_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"ice", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_ICE_SWITCH_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"collision", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_IMAG_AXE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"imagination", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{43d11dc0-a096-4595-8fbe-f95a4a0d951e}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_IMAG_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"imagination", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{3b8ac663-b5da-4aff-8aaf-6ce2fe6ccbde}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_IMAG_ELEVATOR_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"imagination", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_IMAG_HIGH_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"imagination", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{b1bbe65e-330d-4ef6-a534-63e98dd199ec}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_IMAG_LOW_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"imagination", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{3062c5b2-b35a-4935-863f-a8c170aa1444}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_IMAG_PROJECTILE_SPINNER.lua", []() { return new NjSpinnerServer({ u"arrow", u"imagination", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_IMAG_SPAWN_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"imagination", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_IMAG_SWITCH_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"collision", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_LIGHTNING_AXE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"lightning", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{43d11dc0-a096-4595-8fbe-f95a4a0d951e}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_LIGHTNING_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"lightning", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{3b8ac663-b5da-4aff-8aaf-6ce2fe6ccbde}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_LIGHTNING_ELEVATOR_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"lightning", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_LIGHTNING_HIGH_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"lightning", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{b1bbe65e-330d-4ef6-a534-63e98dd199ec}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_LIGHTNING_LOW_BLADE_SPINNER.lua", []() { return new NjSpinnerServer({ u"blade", u"lightning", "{5c30c263-00ae-42a2-80a3-2ae33c8f13fe}", "{40e86d71-084c-4149-884e-ab9b45b694dc}", "{ab21b048-5d1a-40b3-9203-88b376f92087}", "{3062c5b2-b35a-4935-863f-a8c170aa1444}" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_LIGHTNING_PROJECTILE_SPINNER.lua", []() { return new NjSpinnerServer({ u"arrow", u"lightning", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_LIGHTNING_SPAWN_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"lightning", "", "", "", "" }); }}, + {"scripts\\02_server\\Objects\\NJ\\L_NJ_LIGHTNING_SWITCH_SPINNER.lua", []() { return new NjSpinnerServer({ u"elevator", u"collision", "", "", "", "" }); }}, {"scripts\\02_server\\Objects\\L_TURRET.lua", []() {return new ImmovableTurret();}}, {"scripts\\02_server\\Objects\\Hatchlings\\L_HATCHLING_PETS.lua", []() {return new HatchlingPets();}}, {"scripts\\zone\\PROPERTY\\NS\\L_ZONE_NS_PROPERTY.lua", []() {return new ZoneNsProperty();}},