Organize dScripts (#814)

* Organize dScripts

whitespace

Remove parent scope

Remove parent scope from initial setter

Remove debug

Remove helper programs

* Fix NtImagimeterVisibility script

Co-authored-by: aronwk-aaron <aronwk.aaron@gmail.com>
This commit is contained in:
David Markowitz
2022-11-03 10:57:54 -07:00
committed by GitHub
parent b974eed8f5
commit 8d37d9b681
567 changed files with 886 additions and 252 deletions

View File

@@ -0,0 +1,14 @@
#include "BurningTile.h"
#include "SkillComponent.h"
void BurningTile::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) {
if (args == "PlayerEntered") {
auto* skillComponent = sender->GetComponent<SkillComponent>();
if (skillComponent == nullptr) {
return;
}
skillComponent->CalculateBehavior(726, 11723, sender->GetObjectID(), true);
}
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include "CppScripts.h"
class BurningTile : public CppScripts::Script
{
public:
void OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) override;
};

View File

@@ -0,0 +1,31 @@
set(DSCRIPTS_SOURCES_02_SERVER_MAP_NJHUB
"BurningTile.cpp"
"CatapultBaseServer.cpp"
"CatapultBouncerServer.cpp"
"CavePrisonCage.cpp"
"EnemySkeletonSpawner.cpp"
"FallingTile.cpp"
"FlameJetServer.cpp"
"ImaginationShrineServer.cpp"
"Lieutenant.cpp"
"MonCoreNookDoors.cpp"
"MonCoreSmashableDoors.cpp"
"NjColeNPC.cpp"
"NjDragonEmblemChestServer.cpp"
"NjEarthDragonPetServer.cpp"
"NjEarthPetServer.cpp"
"NjGarmadonCelebration.cpp"
"NjJayMissionItems.cpp"
"NjNPCMissionSpinjitzuServer.cpp"
"NjNyaMissionitems.cpp"
"NjScrollChestServer.cpp"
"NjWuNPC.cpp"
"RainOfArrows.cpp")
add_subdirectory(boss_instance)
foreach(file ${DSCRIPTS_SOURCES_02_SERVER_MAP_NJHUB_BOSS_INSTANCE})
set(DSCRIPTS_SOURCES_02_SERVER_MAP_NJHUB ${DSCRIPTS_SOURCES_02_SERVER_MAP_NJHUB} "boss_instance/${file}")
endforeach()
set(DSCRIPTS_SOURCES_02_SERVER_MAP_NJHUB ${DSCRIPTS_SOURCES_02_SERVER_MAP_NJHUB} PARENT_SCOPE)

View File

@@ -0,0 +1,64 @@
#include "CatapultBaseServer.h"
#include "GameMessages.h"
#include "EntityManager.h"
void CatapultBaseServer::OnNotifyObject(Entity* self, Entity* sender, const std::string& name, int32_t param1, int32_t param2) {
if (name == "BouncerBuilt") {
// start a timer for the arm to player the with bouncer animation
self->AddTimer("PlatAnim", .75);
// set the bouncer so we can use it later
self->SetVar(u"Bouncer", sender->GetObjectID());
GameMessages::SendBouncerActiveStatus(sender->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS);
}
}
void CatapultBaseServer::OnTimerDone(Entity* self, std::string timerName) {
if (timerName == "PlatAnim") {
// get the arm asset
const auto arm = EntityManager::Instance()->GetEntitiesInGroup(self->GetVarAsString(u"ArmGroup"));
// tell the arm to the play the platform animation, which is just the arm laying there but with bouncer
for (auto* obj : arm) {
GameMessages::SendPlayAnimation(obj, u"idle-platform");
GameMessages::SendPlayNDAudioEmitter(obj, UNASSIGNED_SYSTEM_ADDRESS, "{8cccf912-69e3-4041-a20b-63e4afafc993}");
// set the art so we can use it again
self->SetVar(u"Arm", obj->GetObjectID());
break;
}
// start a timer till the bouncer actually bounces
self->AddTimer("bounce", 3);
} else if (timerName == "launchAnim") {
// get the arm asset
auto* arm = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(u"Arm"));
if (arm == nullptr) return;
// tell the arm to player the launcher animation
auto animTime = 1;
self->AddTimer("resetArm", animTime);
GameMessages::SendPlayAnimation(arm, u"launch");
} else if (timerName == "bounce") {
auto* bouncer = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(u"Bouncer"));
if (bouncer == nullptr) return;
// bounce all players
bouncer->NotifyObject(bouncer, "bounceAllInProximity"); // Likely to trigger server side bounce, bodging this
// add a delay to play the animation
self->AddTimer("launchAnim", .3);
} else if (timerName == "resetArm") {
auto* arm = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(u"Arm"));
if (arm == nullptr) return;
// set the arm back to natural state
GameMessages::SendPlayAnimation(arm, u"idle");
auto* bouncer = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(u"Bouncer"));
if (bouncer == nullptr) return;
// kill the bouncer
GameMessages::SendNotifyClientObject(bouncer->GetObjectID(), u"TimeToDie");
bouncer->Smash(self->GetObjectID(), VIOLENT);
}
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include "CppScripts.h"
class CatapultBaseServer : public CppScripts::Script {
public:
void OnNotifyObject(Entity* self, Entity* sender, const std::string& name, int32_t param1 = 0, int32_t param2 = 0) override;
void OnTimerDone(Entity* self, std::string timerName) override;
};

View File

@@ -0,0 +1,15 @@
#include "CatapultBouncerServer.h"
#include "GameMessages.h"
#include "EntityManager.h"
void CatapultBouncerServer::OnRebuildComplete(Entity* self, Entity* target) {
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"Built", 0, 0, LWOOBJID_EMPTY, "", UNASSIGNED_SYSTEM_ADDRESS);
self->SetNetworkVar<bool>(u"Built", true);
const auto base = EntityManager::Instance()->GetEntitiesInGroup(self->GetVarAsString(u"BaseGroup"));
for (auto* obj : base) {
obj->NotifyObject(self, "BouncerBuilt");
}
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include "CppScripts.h"
class CatapultBouncerServer : public CppScripts::Script {
public:
void OnRebuildComplete(Entity* self, Entity* target) override;
};

View File

@@ -0,0 +1,215 @@
#include "CavePrisonCage.h"
#include "EntityManager.h"
#include "RebuildComponent.h"
#include "GameMessages.h"
#include "Character.h"
#include "dZoneManager.h"
void CavePrisonCage::OnStartup(Entity* self) {
const auto& myNum = self->GetVar<std::u16string>(u"myNumber");
if (myNum.empty()) {
return;
}
auto* spawner = dZoneManager::Instance()->GetSpawnersByName("PrisonCounterweight_0" + GeneralUtils::UTF16ToWTF8(myNum))[0];
self->SetVar<Spawner*>(u"CWSpawner", spawner);
Setup(self, spawner);
}
void CavePrisonCage::Setup(Entity* self, Spawner* spawner) {
SpawnCounterweight(self, spawner);
NiPoint3 mypos = self->GetPosition();
NiQuaternion myrot = self->GetRotation();
mypos.y += 1.5;
mypos.z -= 0.5;
EntityInfo info{};
info.lot = m_Villagers[self->GetVarAs<int32_t>(u"myNumber") - 1];
info.pos = mypos;
info.rot = myrot;
info.spawnerID = self->GetObjectID();
// Spawn the villager inside the jail
auto* entity = EntityManager::Instance()->CreateEntity(info);
// Save the villeger ID
self->SetVar<LWOOBJID>(u"villager", entity->GetObjectID());
// Construct the entity
EntityManager::Instance()->ConstructEntity(entity);
}
void CavePrisonCage::OnRebuildNotifyState(Entity* self, eRebuildState state) {
if (state != eRebuildState::REBUILD_RESETTING) {
return;
}
auto* spawner = self->GetVar<Spawner*>(u"CWSpawner");
if (spawner == nullptr) {
return;
}
spawner->Reset();
SpawnCounterweight(self, spawner);
}
void CavePrisonCage::SpawnCounterweight(Entity* self, Spawner* spawner) {
spawner->Reset();
auto* counterweight = spawner->Spawn();
self->SetVar<LWOOBJID>(u"Counterweight", counterweight->GetObjectID());
auto* rebuildComponent = counterweight->GetComponent<RebuildComponent>();
if (rebuildComponent != nullptr) {
rebuildComponent->AddRebuildStateCallback([this, self](eRebuildState state) {
OnRebuildNotifyState(self, state);
});
rebuildComponent->AddRebuildCompleteCallback([this, self](Entity* user) {
// The counterweight is a simple mover, which is not implemented, so we'll just set it's position
auto* counterweight = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(u"Counterweight"));
if (counterweight == nullptr) {
return;
}
// Move the counterweight down 2 units
counterweight->SetPosition(counterweight->GetPosition() + NiPoint3(0, -2, 0));
// Serialize the counterweight
EntityManager::Instance()->SerializeEntity(counterweight);
// notifyPlatformAtLastWaypoint
// Save the userID as Builder
self->SetVar<LWOOBJID>(u"Builder", user->GetObjectID());
// Get the button and make sure it still exists
auto* button = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(u"Button"));
if (button == nullptr) {
return;
}
// Play the 'down' animation on the button
GameMessages::SendPlayAnimation(button, u"down");
// Setup a timer named 'buttonGoingDown' to be triggered in 5 seconds
self->AddTimer("buttonGoingDown", 5.0f);
});
}
if (self->GetVar<LWOOBJID>(u"Button")) {
return;
}
GetButton(self);
}
void CavePrisonCage::GetButton(Entity* self) {
const auto buttons = EntityManager::Instance()->GetEntitiesInGroup("PrisonButton_0" + std::to_string(self->GetVarAs<int32_t>(u"myNumber")));
if (buttons.size() == 0) {
// Try again in 0.5 seconds
self->AddCallbackTimer(0.5, [this, self]() {
GetButton(self);
});
return;
}
auto* button = buttons[0];
self->SetVar<LWOOBJID>(u"Button", button->GetObjectID());
}
void CavePrisonCage::OnTimerDone(Entity* self, std::string timerName) {
// the anim of the button down is over
if (timerName == "buttonGoingDown") {
// Play the 'up' animation
GameMessages::SendPlayAnimation(self, u"up");
// Setup a timer named 'CageOpen' to be triggered in 1 second
self->AddTimer("CageOpen", 1.0f);
} else if (timerName == "CageOpen") {
// play the idle open anim
GameMessages::SendPlayAnimation(self, u"idle-up");
// Get the villeger
auto* villager = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(u"villager"));
if (villager == nullptr) {
return;
}
GameMessages::SendNotifyClientObject(villager->GetObjectID(), u"TimeToChat", 0, 0, LWOOBJID_EMPTY, "", UNASSIGNED_SYSTEM_ADDRESS);
// Get the builder and make sure it still exists
auto* builder = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(u"Builder"));
if (builder == nullptr) {
return;
}
const auto flagNum = 2020 + self->GetVarAs<int32_t>(u"myNumber");
// Set the flag on the builder character
builder->GetCharacter()->SetPlayerFlag(flagNum, true);
// Setup a timer named 'VillagerEscape' to be triggered in 5 seconds
self->AddTimer("VillagerEscape", 5.0f);
} else if (timerName == "VillagerEscape") {
// Get the villeger and make sure it still exists
auto* villager = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(u"villager"));
if (villager == nullptr) {
return;
}
// Kill the villager
villager->Kill();
// Setup a timer named 'SmashCounterweight' to be triggered in 2 seconds
self->AddTimer("SmashCounterweight", 2.0f);
} else if (timerName == "SmashCounterweight") {
// Get the counterweight and make sure it still exists
auto* counterweight = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(u"Counterweight"));
if (counterweight == nullptr) {
return;
}
// Smash the counterweight
counterweight->Smash();
// Get the button and make sure it still exists
auto* button = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(u"Button"));
if (button == nullptr) {
return;
}
// Play the 'up' animation on the button
GameMessages::SendPlayAnimation(button, u"up");
// Setup a timer named 'CageClosed' to be triggered in 1 second
self->AddTimer("CageClosed", 1.0f);
} else if (timerName == "CageClosed") {
// play the idle closed anim
GameMessages::SendPlayAnimation(self, u"idle");
// Setup a timer named 'ResetPrison' to be triggered in 10 seconds
self->AddTimer("ResetPrison", 10.0f);
} else if (timerName == "ResetPrison") {
Setup(self, self->GetVar<Spawner*>(u"CWSpawner"));
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "CppScripts.h"
class CavePrisonCage : public CppScripts::Script {
public:
void OnStartup(Entity* self) override;
void Setup(Entity* self, Spawner* spawner);
void OnRebuildNotifyState(Entity* self, eRebuildState state) override;
void SpawnCounterweight(Entity* self, Spawner* spawner);
void GetButton(Entity* self);
void OnTimerDone(Entity* self, std::string timerName) override;
private:
std::vector<LOT> m_Villagers = { 15851, 15866, 15922, 15924, 15927, 15929 };
std::vector<int32_t> m_Missions = { 1801, 2039 };
};

View File

@@ -0,0 +1,75 @@
#include "EnemySkeletonSpawner.h"
#include "SkillComponent.h"
#include "RenderComponent.h"
#include "EntityManager.h"
void EnemySkeletonSpawner::OnStartup(Entity* self) {
self->SetProximityRadius(15, "ronin");
auto* skillComponent = self->GetComponent<SkillComponent>();
if (skillComponent != nullptr) {
skillComponent->CalculateBehavior(1127, 24812, LWOOBJID_EMPTY, true);
}
}
void EnemySkeletonSpawner::OnTimerDone(Entity* self, std::string timerName) {
if (timerName == "hatchTime") {
auto* renderComponent = self->GetComponent<RenderComponent>();
if (renderComponent != nullptr) {
renderComponent->PlayEffect(644, u"create", "BurstFX1");
}
EntityInfo info{};
info.lot = 14024;
info.pos = self->GetPosition();
info.rot = self->GetRotation();
info.spawnerID = self->GetObjectID();
auto* spawnedEntity = EntityManager::Instance()->CreateEntity(info);
if (spawnedEntity == nullptr) {
return;
}
EntityManager::Instance()->ConstructEntity(spawnedEntity);
spawnedEntity->AddCallbackTimer(60, [spawnedEntity]() {
spawnedEntity->Smash(spawnedEntity->GetObjectID());
});
self->Smash(self->GetObjectID());
}
}
void EnemySkeletonSpawner::OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) {
if (entering->IsPlayer() && name == "ronin" && status == "ENTER" && !self->GetVar<bool>(u"hatching")) {
StartHatching(self);
auto* skillComponent = self->GetComponent<SkillComponent>();
if (skillComponent != nullptr) {
skillComponent->CalculateBehavior(305, 3568, LWOOBJID_EMPTY);
}
}
}
void EnemySkeletonSpawner::OnHit(Entity* self, Entity* attacker) {
if (!self->GetVar<bool>(u"hatching")) {
StartHatching(self);
}
}
void EnemySkeletonSpawner::StartHatching(Entity* self) {
self->SetVar(u"hatching", true);
auto* renderComponent = self->GetComponent<RenderComponent>();
if (renderComponent != nullptr) {
renderComponent->PlayEffect(9017, u"cast", "WakeUpFX1");
renderComponent->PlayEffect(9018, u"burst", "WakeUpFX1");
}
self->AddTimer("hatchTime", 2);
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "CppScripts.h"
class EnemySkeletonSpawner final : public CppScripts::Script {
public:
void OnStartup(Entity* self) override;
void OnTimerDone(Entity* self, std::string timerName) override;
void OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) override;
void OnHit(Entity* self, Entity* attacker) override;
void StartHatching(Entity* self);
};

View File

@@ -0,0 +1,55 @@
#include "FallingTile.h"
#include "MovingPlatformComponent.h"
#include "GameMessages.h"
void FallingTile::OnStartup(Entity* self) {
auto* movingPlatfromComponent = self->GetComponent<MovingPlatformComponent>();
if (movingPlatfromComponent == nullptr) {
return;
}
movingPlatfromComponent->SetSerialized(true);
}
void FallingTile::OnCollisionPhantom(Entity* self, Entity* target) {
if (self->GetVar<bool>(u"AboutToFall")) {
return;
}
self->AddTimer("flipTime", 0.75f);
self->SetVar<bool>(u"AboutToFall", true);
self->SetNetworkVar<float>(u"startEffect", 2);
}
void FallingTile::OnWaypointReached(Entity* self, uint32_t waypointIndex) {
if (waypointIndex == 1) {
} else if (waypointIndex == 0) {
}
}
void FallingTile::OnTimerDone(Entity* self, std::string timerName) {
auto* movingPlatfromComponent = self->GetComponent<MovingPlatformComponent>();
if (movingPlatfromComponent == nullptr) {
return;
}
if (timerName == "flipTime") {
self->AddTimer("flipBack", 2.0f);
self->SetNetworkVar<float>(u"stopEffect", 3);
movingPlatfromComponent->GotoWaypoint(1);
GameMessages::SendPlayFXEffect(self->GetObjectID(), -1, u"down", "down");
} else if (timerName == "flipBack") {
self->SetVar<bool>(u"AboutToFall", false);
movingPlatfromComponent->GotoWaypoint(0);
GameMessages::SendPlayFXEffect(self->GetObjectID(), -1, u"up", "up");
}
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "CppScripts.h"
class FallingTile : public CppScripts::Script
{
public:
void OnStartup(Entity* self) override;
void OnCollisionPhantom(Entity* self, Entity* target) override;
void OnWaypointReached(Entity* self, uint32_t waypointIndex) override;
void OnTimerDone(Entity* self, std::string timerName) override;
};

View File

@@ -0,0 +1,47 @@
#include "FlameJetServer.h"
#include "SkillComponent.h"
#include "GameMessages.h"
void FlameJetServer::OnStartup(Entity* self) {
if (self->GetVar<bool>(u"NotActive")) {
return;
}
self->SetNetworkVar<bool>(u"FlameOn", true);
}
void FlameJetServer::OnCollisionPhantom(Entity* self, Entity* target) {
if (!target->IsPlayer()) {
return;
}
if (!self->GetNetworkVar<bool>(u"FlameOn")) {
return;
}
auto* skillComponent = target->GetComponent<SkillComponent>();
if (skillComponent == nullptr) {
return;
}
skillComponent->CalculateBehavior(726, 11723, target->GetObjectID(), true);
auto dir = target->GetRotation().GetForwardVector();
dir.y = 25;
dir.x = -dir.x * 15;
dir.z = -dir.z * 15;
GameMessages::SendKnockback(target->GetObjectID(), self->GetObjectID(), self->GetObjectID(), 1000, dir);
}
void FlameJetServer::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) {
Game::logger->Log("FlameJetServer::OnFireEventServerSide", "Event: %s", args.c_str());
if (args == "OnActivated") {
self->SetNetworkVar<bool>(u"FlameOn", false);
} else if (args == "OnDectivated") {
self->SetNetworkVar<bool>(u"FlameOn", true);
}
}

View File

@@ -0,0 +1,10 @@
#pragma once
#include "CppScripts.h"
class FlameJetServer : public CppScripts::Script
{
public:
void OnStartup(Entity* self) override;
void OnCollisionPhantom(Entity* self, Entity* target) override;
void OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) override;
};

View File

@@ -0,0 +1,16 @@
#include "ImaginationShrineServer.h"
#include "RebuildComponent.h"
void ImaginationShrineServer::OnUse(Entity* self, Entity* user) {
// If the rebuild component is complete, use the shrine
auto* rebuildComponent = self->GetComponent<RebuildComponent>();
if (rebuildComponent == nullptr) {
return;
}
if (rebuildComponent->GetState() == REBUILD_COMPLETED) {
// Use the shrine
BaseUse(self, user);
}
}

View File

@@ -0,0 +1,10 @@
#pragma once
#include "CppScripts.h"
#include "BaseInteractDropLootServer.h"
class ImaginationShrineServer : public BaseInteractDropLootServer
{
public:
void OnUse(Entity* self, Entity* user) override;
};

View File

@@ -0,0 +1,44 @@
#include "Lieutenant.h"
#include "SkillComponent.h"
#include "dZoneManager.h"
void Lieutenant::OnStartup(Entity* self) {
auto* skillComponent = self->GetComponent<SkillComponent>();
if (skillComponent == nullptr) {
return;
}
skillComponent->CalculateBehavior(1127, 24812, self->GetObjectID(), true);
}
void Lieutenant::OnDie(Entity* self, Entity* killer) {
const auto myLOT = self->GetLOT();
std::string spawnerName;
switch (myLOT) {
case 16047:
spawnerName = "EarthShrine_ERail";
break;
case 16050:
spawnerName = "IceShrine_QBBouncer";
break;
case 16049:
spawnerName = "LightningShrine_LRail";
break;
default:
return;
}
const auto spawners = dZoneManager::Instance()->GetSpawnersByName(spawnerName);
if (spawners.empty()) {
return;
}
for (auto* spawner : spawners) {
spawner->Reset();
spawner->Activate();
}
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "CppScripts.h"
class Lieutenant : public CppScripts::Script
{
public:
void OnStartup(Entity* self) override;
void OnDie(Entity* self, Entity* killer) override;
};

View File

@@ -0,0 +1,37 @@
#include "MonCoreNookDoors.h"
#include "dZoneManager.h"
void MonCoreNookDoors::OnStartup(Entity* self) {
SpawnDoor(self);
}
void MonCoreNookDoors::SpawnDoor(Entity* self) {
const auto doorNum = self->GetVarAsString(u"number");
if (doorNum.empty()) {
return;
}
const auto spawners = dZoneManager::Instance()->GetSpawnersByName("MonCoreNookDoor0" + doorNum);
if (spawners.empty()) {
return;
}
auto* spawner = spawners[0];
spawner->Reset();
spawner->Activate();
}
void MonCoreNookDoors::OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) {
if (args == "DoorSmashed") {
self->AddTimer("RespawnDoor", 30);
}
}
void MonCoreNookDoors::OnTimerDone(Entity* self, std::string timerName) {
if (timerName == "RespawnDoor") {
SpawnDoor(self);
}
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include "CppScripts.h"
class MonCoreNookDoors : public CppScripts::Script {
public:
void OnStartup(Entity* self) override;
void SpawnDoor(Entity* self);
void OnFireEventServerSide(Entity* self, Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) override;
void OnTimerDone(Entity* self, std::string timerName) override;
};

View File

@@ -0,0 +1,21 @@
#include "MonCoreSmashableDoors.h"
#include "EntityManager.h"
void MonCoreSmashableDoors::OnDie(Entity* self, Entity* killer) {
auto myNum = self->GetVarAsString(u"spawner_name");
myNum = myNum.substr(myNum.length() - 1, 1);
auto triggerGroup = "CoreNookTrig0" + myNum;
// Get the trigger
auto triggers = EntityManager::Instance()->GetEntitiesInGroup(triggerGroup);
if (triggers.empty()) {
return;
}
for (auto trigger : triggers) {
trigger->OnFireEventServerSide(self, "DoorSmashed");
}
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include "CppScripts.h"
class MonCoreSmashableDoors : public CppScripts::Script {
public:
void OnDie(Entity* self, Entity* killer) override;
};

View File

@@ -0,0 +1,48 @@
#include "NjColeNPC.h"
#include "MissionComponent.h"
#include "InventoryComponent.h"
void NjColeNPC::OnEmoteReceived(Entity* self, int32_t emote, Entity* target) {
if (emote != 393) {
return;
}
auto* inventoryComponent = target->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return;
}
if (!inventoryComponent->IsEquipped(14499) && !inventoryComponent->IsEquipped(16644)) {
return;
}
auto* missionComponent = target->GetComponent<MissionComponent>();
if (missionComponent == nullptr) {
return;
}
missionComponent->ForceProgressTaskType(1818, 1, 1);
}
void NjColeNPC::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, MissionState missionState) {
NjNPCMissionSpinjitzuServer::OnMissionDialogueOK(self, target, missionID, missionState);
if (missionID == 1818 && missionState >= MissionState::MISSION_STATE_READY_TO_COMPLETE) {
auto* missionComponent = target->GetComponent<MissionComponent>();
auto* inventoryComponent = target->GetComponent<InventoryComponent>();
if (missionComponent == nullptr || inventoryComponent == nullptr) {
return;
}
if (inventoryComponent->GetLotCount(14499) > 0) {
inventoryComponent->RemoveItem(14499, 1);
} else {
return;
}
inventoryComponent->AddItem(16644, 1, eLootSourceType::LOOT_SOURCE_NONE);
}
}

View File

@@ -0,0 +1,7 @@
#include "NjNPCMissionSpinjitzuServer.h"
class NjColeNPC : public NjNPCMissionSpinjitzuServer {
void OnEmoteReceived(Entity* self, int32_t emote, Entity* target) override;
void OnMissionDialogueOK(Entity* self, Entity* target, int missionID, MissionState missionState) override;
};

View File

@@ -0,0 +1,15 @@
#include "NjDragonEmblemChestServer.h"
#include "Character.h"
#include "DestroyableComponent.h"
void NjDragonEmblemChestServer::OnUse(Entity* self, Entity* user) {
auto* character = user->GetCharacter();
if (character != nullptr) {
character->SetPlayerFlag(NJ_WU_SHOW_DAILY_CHEST, false);
}
auto* destroyable = self->GetComponent<DestroyableComponent>();
if (destroyable != nullptr) {
LootGenerator::Instance().DropLoot(user, self, destroyable->GetLootMatrixID(), 0, 0);
}
}

View File

@@ -0,0 +1,6 @@
#pragma once
#include "CppScripts.h"
class NjDragonEmblemChestServer : public CppScripts::Script {
void OnUse(Entity* self, Entity* user) override;
};

View File

@@ -0,0 +1,10 @@
#include "NjEarthDragonPetServer.h"
#include "Entity.h"
void NjEarthDragonPetServer::SetVariables(Entity* self) {
self->SetVar<LOT>(u"petLOT", 16210);
self->SetVar<std::string>(u"petType", "earthpet");
self->SetVar<uint32_t>(u"maxPets", 3);
self->SetVar<std::u16string>(u"spawnAnim", u"spawn");
self->SetVar<std::u16string>(u"spawnCinematic", u"EarthPetSpawn");
}

View File

@@ -0,0 +1,6 @@
#pragma once
#include "SpawnPetBaseServer.h"
class NjEarthDragonPetServer : public SpawnPetBaseServer {
void SetVariables(Entity* self) override;
};

View File

@@ -0,0 +1,12 @@
#include "NjEarthPetServer.h"
#include "PetComponent.h"
void NjEarthPetServer::OnStartup(Entity* self) {
auto* petComponent = self->GetComponent<PetComponent>();
if (petComponent == nullptr || petComponent->GetOwnerId() != LWOOBJID_EMPTY)
return;
// Removes the chocolate bars
petComponent->SetPreconditions(const_cast<std::string&>(m_Precondition));
PetFromObjectServer::OnStartup(self);
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include "PetFromObjectServer.h"
class NjEarthPetServer : public PetFromObjectServer {
void OnStartup(Entity* self) override;
const std::string m_Precondition = "279";
};

View File

@@ -0,0 +1,17 @@
#include "NjGarmadonCelebration.h"
#include "Character.h"
#include "GameMessages.h"
void NjGarmadonCelebration::OnCollisionPhantom(Entity* self, Entity* target) {
auto* character = target->GetCharacter();
if (character == nullptr) {
return;
}
if (!character->GetPlayerFlag(ePlayerFlags::NJ_GARMADON_CINEMATIC_SEEN)) {
character->SetPlayerFlag(ePlayerFlags::NJ_GARMADON_CINEMATIC_SEEN, true);
GameMessages::SendStartCelebrationEffect(target, target->GetSystemAddress(), GarmadonCelebrationID);
}
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include "CppScripts.h"
class NjGarmadonCelebration : public CppScripts::Script {
void OnCollisionPhantom(Entity* self, Entity* target) override;
private:
const int32_t GarmadonCelebrationID = 23;
};

View File

@@ -0,0 +1,13 @@
#include "NjJayMissionItems.h"
void NjJayMissionItems::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, MissionState missionState) {
NjNPCMissionSpinjitzuServer::OnMissionDialogueOK(self, target, missionID, missionState);
NPCAddRemoveItem::OnMissionDialogueOK(self, target, missionID, missionState);
}
std::map<uint32_t, std::vector<ItemSetting>> NjJayMissionItems::GetSettings() {
return {
{1789, {{{14474},false, true}}},
{1927, {{{14493},false, true}}}
};
}

View File

@@ -0,0 +1,10 @@
#pragma once
#include "NjNPCMissionSpinjitzuServer.h"
#include "NPCAddRemoveItem.h"
#include <vector>
#include <map>
class NjJayMissionItems : public NjNPCMissionSpinjitzuServer, NPCAddRemoveItem {
void OnMissionDialogueOK(Entity* self, Entity* target, int missionID, MissionState missionState) override;
std::map<uint32_t, std::vector<ItemSetting>> GetSettings() override;
};

View File

@@ -0,0 +1,24 @@
#include "NjNPCMissionSpinjitzuServer.h"
#include "Character.h"
#include "EntityManager.h"
void NjNPCMissionSpinjitzuServer::OnMissionDialogueOK(Entity* self, Entity* target, int missionID,
MissionState missionState) {
const auto& element = self->GetVar<std::u16string>(ElementVariable);
if (missionID == ElementMissions.at(element) && missionState >= MissionState::MISSION_STATE_READY_TO_COMPLETE) {
const auto targetID = target->GetObjectID();
// Wait for an animation to complete and flag that the player has learned spinjitzu
self->AddCallbackTimer(5.0f, [targetID, element]() {
auto* target = EntityManager::Instance()->GetEntity(targetID);
if (target != nullptr) {
auto* character = target->GetCharacter();
if (character != nullptr) {
character->SetPlayerFlag(ElementFlags.at(element), true);
}
}
});
}
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "CppScripts.h"
#include <map>
static std::map<std::u16string, uint32_t> ElementFlags = {
{u"earth", ePlayerFlags::NJ_EARTH_SPINJITZU},
{u"lightning", ePlayerFlags::NJ_LIGHTNING_SPINJITZU},
{u"ice", ePlayerFlags::NJ_ICE_SPINJITZU},
{u"fire", ePlayerFlags::NJ_FIRE_SPINJITZU}
};
static std::map<std::u16string, uint32_t> ElementMissions = {
{u"earth", 1796},
{u"lightning", 1952},
{u"ice", 1959},
{u"fire", 1962},
};
class NjNPCMissionSpinjitzuServer : public CppScripts::Script {
public:
void OnMissionDialogueOK(Entity* self, Entity* target, int missionID, MissionState missionState) override;
private:
const std::u16string ElementVariable = u"element";
};

View File

@@ -0,0 +1,8 @@
#include "NjNyaMissionitems.h"
std::map<uint32_t, std::vector<ItemSetting>> NjNyaMissionitems::GetSettings() {
return {
{1809, {{{14472}, true, false}}},
{1821, {{{14500}, false, true}}}
};
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include "NPCAddRemoveItem.h"
#include <map>
#include <vector>
class NjNyaMissionitems : public NPCAddRemoveItem {
std::map<uint32_t, std::vector<ItemSetting>> GetSettings() override;
};

View File

@@ -0,0 +1,17 @@
#include "NjScrollChestServer.h"
#include "InventoryComponent.h"
void NjScrollChestServer::OnUse(Entity* self, Entity* user) {
const auto keyLOT = self->GetVar<LOT>(u"KeyNum");
const auto rewardItemLOT = self->GetVar<LOT>(u"openItemID");
auto* playerInventory = user->GetComponent<InventoryComponent>();
if (playerInventory != nullptr && playerInventory->GetLotCount(keyLOT) == 1) {
// Check for the key and remove
playerInventory->RemoveItem(keyLOT, 1);
// Reward the player with the item set
playerInventory->AddItem(rewardItemLOT, 1, eLootSourceType::LOOT_SOURCE_NONE);
}
}

View File

@@ -0,0 +1,6 @@
#pragma once
#include "CppScripts.h"
class NjScrollChestServer : public CppScripts::Script {
void OnUse(Entity* self, Entity* user) override;
};

View File

@@ -0,0 +1,63 @@
#include "NjWuNPC.h"
#include "MissionComponent.h"
#include "Character.h"
#include "EntityManager.h"
#include "GameMessages.h"
void NjWuNPC::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, MissionState missionState) {
// The Dragon statue daily mission
if (missionID == m_MainDragonMissionID) {
auto* character = target->GetCharacter();
auto* missionComponent = target->GetComponent<MissionComponent>();
if (character == nullptr || missionComponent == nullptr)
return;
switch (missionState) {
case MissionState::MISSION_STATE_AVAILABLE:
case MissionState::MISSION_STATE_COMPLETE_AVAILABLE:
{
// Reset the sub missions
for (const auto& subMissionID : m_SubDragonMissionIDs) {
missionComponent->RemoveMission(subMissionID);
missionComponent->AcceptMission(subMissionID);
}
character->SetPlayerFlag(ePlayerFlags::NJ_WU_SHOW_DAILY_CHEST, false);
// Hide the chest
for (auto* chest : EntityManager::Instance()->GetEntitiesInGroup(m_DragonChestGroup)) {
GameMessages::SendNotifyClientObject(chest->GetObjectID(), m_ShowChestNotification, 0, -1,
target->GetObjectID(), "", target->GetSystemAddress());
}
return;
}
case MissionState::MISSION_STATE_READY_TO_COMPLETE:
case MissionState::MISSION_STATE_COMPLETE_READY_TO_COMPLETE:
{
character->SetPlayerFlag(NJ_WU_SHOW_DAILY_CHEST, true);
// Show the chest
for (auto* chest : EntityManager::Instance()->GetEntitiesInGroup(m_DragonChestGroup)) {
GameMessages::SendNotifyClientObject(chest->GetObjectID(), m_ShowChestNotification, 1, -1,
target->GetObjectID(), "", target->GetSystemAddress());
}
auto playerID = target->GetObjectID();
self->AddCallbackTimer(5.0f, [this, playerID]() {
auto* player = EntityManager::Instance()->GetEntity(playerID);
if (player == nullptr)
return;
// Stop the dragon effects
for (auto* dragon : EntityManager::Instance()->GetEntitiesInGroup(m_DragonStatueGroup)) {
GameMessages::SendStopFXEffect(dragon, true, "on");
}
});
}
default:
return;
}
}
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "AmTemplateSkillVolume.h"
class NjWuNPC : public AmTemplateSkillVolume {
void OnMissionDialogueOK(Entity* self, Entity* target, int missionID, MissionState missionState) override;
const uint32_t m_MainDragonMissionID = 2040;
const std::vector<uint32_t> m_SubDragonMissionIDs = { 2064, 2065, 2066, 2067 };
// Groups and variables
const std::string m_DragonChestGroup = "DragonEmblemChest";
const std::string m_DragonStatueGroup = "Minidragons";
const std::u16string m_ShowChestNotification = u"showChest";
};

View File

@@ -0,0 +1,70 @@
#include "RainOfArrows.h"
#include "EntityManager.h"
#include "SkillComponent.h"
#include "GameMessages.h"
void RainOfArrows::OnStartup(Entity* self) {
}
void RainOfArrows::OnRebuildComplete(Entity* self, Entity* target) {
auto myPos = self->GetPosition();
auto myRot = self->GetRotation();
EntityInfo info;
info.lot = m_ArrowFXObject;
info.pos = myPos;
info.rot = myRot;
info.spawnerID = self->GetObjectID();
auto* entity = EntityManager::Instance()->CreateEntity(info);
EntityManager::Instance()->ConstructEntity(entity);
self->SetVar<LWOOBJID>(u"ChildFX", entity->GetObjectID());
self->SetVar<LWOOBJID>(u"playerID", target->GetObjectID());
self->AddTimer("ArrowsIncoming", m_ArrowDelay);
self->AddTimer("PlayArrowSound", m_ArrowDelay - 4);
}
void RainOfArrows::OnTimerDone(Entity* self, std::string timerName) {
auto* child = EntityManager::Instance()->GetEntity(
self->GetVar<LWOOBJID>(u"ChildFX")
);
auto* player = EntityManager::Instance()->GetEntity(
self->GetVar<LWOOBJID>(u"playerID")
);
if (timerName == "ArrowsIncoming") {
if (child == nullptr) {
return;
}
auto* skillComponent = child->GetComponent<SkillComponent>();
if (skillComponent == nullptr) {
return;
}
skillComponent->CalculateBehavior(
m_ArrowSkill,
m_ArrowBehavior,
LWOOBJID_EMPTY,
true,
false,
player != nullptr ? player->GetObjectID() : LWOOBJID_EMPTY
);
self->AddTimer("FireSkill", 0.7f);
} else if (timerName == "PlayArrowSound") {
GameMessages::SendPlayNDAudioEmitter(self, UNASSIGNED_SYSTEM_ADDRESS, m_ArrowsGUID);
} else if (timerName == "FireSkill") {
if (child != nullptr) {
child->Smash();
}
self->Smash(player != nullptr ? player->GetObjectID() : LWOOBJID_EMPTY);
}
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include "CppScripts.h"
class RainOfArrows : public CppScripts::Script
{
public:
void OnStartup(Entity* self) override;
void OnRebuildComplete(Entity* self, Entity* target) override;
void OnTimerDone(Entity* self, std::string timerName) override;
private:
LOT m_ArrowFXObject = 15850;
int32_t m_ArrowSkill = 1482;
int32_t m_ArrowBehavior = 36680;
int32_t m_ArrowDelay = 6;
std::string m_ArrowsGUID = "{532cba3c-54da-446c-986b-128af9647bdb}";
};

View File

@@ -0,0 +1,3 @@
set(DSCRIPTS_SOURCES_02_SERVER_MAP_NJHUB_BOSS_INSTANCE
"NjMonastryBossInstance.cpp"
PARENT_SCOPE)

View File

@@ -0,0 +1,523 @@
#include "NjMonastryBossInstance.h"
#include "RebuildComponent.h"
#include "DestroyableComponent.h"
#include "EntityManager.h"
#include "dZoneManager.h"
#include "GameMessages.h"
#include "BaseCombatAIComponent.h"
#include "BuffComponent.h"
#include "SkillComponent.h"
#include "TeamManager.h"
#include <algorithm>
// // // // // // //
// Event handling //
// // // // // // //
void NjMonastryBossInstance::OnStartup(Entity* self) {
auto spawnerNames = std::vector<std::string>{ LedgeFrakjawSpawner, LowerFrakjawSpawner, BaseEnemiesSpawner + std::to_string(1),
BaseEnemiesSpawner + std::to_string(2), BaseEnemiesSpawner + std::to_string(3),
BaseEnemiesSpawner + std::to_string(4), CounterweightSpawner };
// Add a notification request for all the spawned entities, corresponds to notifySpawnedObjectLoaded
for (const auto& spawnerName : spawnerNames) {
for (auto* spawner : dZoneManager::Instance()->GetSpawnersByName(spawnerName)) {
spawner->AddEntitySpawnedCallback([self, this](Entity* entity) {
const auto lot = entity->GetLOT();
switch (lot) {
case LedgedFrakjawLOT:
NjMonastryBossInstance::HandleLedgedFrakjawSpawned(self, entity);
return;
case CounterWeightLOT:
NjMonastryBossInstance::HandleCounterWeightSpawned(self, entity);
return;
case LowerFrakjawLOT:
NjMonastryBossInstance::HandleLowerFrakjawSpawned(self, entity);
return;
default:
NjMonastryBossInstance::HandleWaveEnemySpawned(self, entity);
return;
}
});
}
}
}
void NjMonastryBossInstance::OnPlayerLoaded(Entity* self, Entity* player) {
ActivityTimerStop(self, WaitingForPlayersTimer);
// Join the player in the activity
UpdatePlayer(self, player->GetObjectID());
// Buff the player
auto* destroyableComponent = player->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr) {
destroyableComponent->SetHealth((int32_t)destroyableComponent->GetMaxHealth());
destroyableComponent->SetArmor((int32_t)destroyableComponent->GetMaxArmor());
destroyableComponent->SetImagination((int32_t)destroyableComponent->GetMaxImagination());
}
// Add player ID to instance
auto totalPlayersLoaded = self->GetVar<std::vector<LWOOBJID>>(TotalPlayersLoadedVariable);
totalPlayersLoaded.push_back(player->GetObjectID());
// Properly position the player
self->SetVar<std::vector<LWOOBJID>>(TotalPlayersLoadedVariable, totalPlayersLoaded);
// This was always spawning all players at position one before and other values cause players to be invisible.
TeleportPlayer(player, 1);
// Large teams face a tougher challenge
if (totalPlayersLoaded.size() >= 3)
self->SetVar<bool>(LargeTeamVariable, true);
// Start the game if all players in the team have loaded
auto* team = TeamManager::Instance()->GetTeam(player->GetObjectID());
if (team == nullptr || totalPlayersLoaded.size() == team->members.size()) {
StartFight(self);
return;
}
self->AddCallbackTimer(0.0f, [self, player]() {
if (player != nullptr) {
// If we don't have enough players yet, wait for the others to load and notify the client to play a cool cinematic
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"PlayerLoaded", 0, 0,
player->GetObjectID(), "", player->GetSystemAddress());
}
});
ActivityTimerStart(self, WaitingForPlayersTimer, 45.0f, 45.0f);
}
void NjMonastryBossInstance::OnPlayerExit(Entity* self, Entity* player) {
UpdatePlayer(self, player->GetObjectID(), true);
// Fetch the total players loaded from the vars
auto totalPlayersLoaded = self->GetVar<std::vector<LWOOBJID> >(TotalPlayersLoadedVariable);
// Find the player to remove
auto playerToRemove = std::find(totalPlayersLoaded.begin(), totalPlayersLoaded.end(), player->GetObjectID());
// If we found the player remove them from out list of players
if (playerToRemove != totalPlayersLoaded.end()) {
totalPlayersLoaded.erase(playerToRemove);
} else {
Game::logger->Log("NjMonastryBossInstance", "Failed to remove player at exit.");
}
// Set the players loaded var back
self->SetVar<std::vector<LWOOBJID>>(TotalPlayersLoadedVariable, totalPlayersLoaded);
// Since this is an exit method, check if enough players have left. If enough have left
// resize the instance to account for such.
if (totalPlayersLoaded.size() <= 2) self->SetVar<bool>(LargeTeamVariable, false);
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"PlayerLeft", 0, 0, player->GetObjectID(), "", UNASSIGNED_SYSTEM_ADDRESS);
}
void NjMonastryBossInstance::OnActivityTimerDone(Entity* self, const std::string& name) {
auto split = GeneralUtils::SplitString(name, TimerSplitChar);
auto timerName = split[0];
auto objectID = split.size() > 1 ? (LWOOBJID)std::stoull(split[1]) : LWOOBJID_EMPTY;
if (timerName == WaitingForPlayersTimer) {
StartFight(self);
} else if (timerName == SpawnNextWaveTimer) {
auto* frakjaw = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(LedgeFrakjawVariable));
if (frakjaw != nullptr) {
SummonWave(self, frakjaw);
}
} else if (timerName == SpawnWaveTimer) {
auto wave = self->GetVar<uint32_t>(WaveNumberVariable);
self->SetVar<uint32_t>(WaveNumberVariable, wave + 1);
self->SetVar<uint32_t>(TotalAliveInWaveVariable, 0);
if (wave < m_Waves.size()) {
auto waves = m_Waves.at(wave);
auto counter = 0;
for (const auto& waveEnemy : waves) {
const auto numberToSpawn = self->GetVar<bool>(LargeTeamVariable)
? waveEnemy.largeNumber : waveEnemy.smallNumber;
auto spawnIndex = counter % 4 + 1;
SpawnOnNetwork(self, waveEnemy.lot, numberToSpawn, BaseEnemiesSpawner + std::to_string(spawnIndex));
counter++;
}
}
} else if (timerName + TimerSplitChar == UnstunTimer) {
auto* entity = EntityManager::Instance()->GetEntity(objectID);
if (entity != nullptr) {
auto* combatAI = entity->GetComponent<BaseCombatAIComponent>();
if (combatAI != nullptr) {
combatAI->SetDisabled(false);
}
}
} else if (timerName == SpawnCounterWeightTimer) {
auto spawners = dZoneManager::Instance()->GetSpawnersByName(CounterweightSpawner);
if (!spawners.empty()) {
// Spawn the counter weight at a specific waypoint, there's one for each round
auto* spawner = spawners.front();
spawner->Spawn({
spawner->m_Info.nodes.at((self->GetVar<uint32_t>(WaveNumberVariable) - 1) % 3)
}, true);
}
} else if (timerName == LowerFrakjawCamTimer) {
// Destroy the frakjaw on the ledge
auto* ledgeFrakjaw = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(LedgeFrakjawVariable));
if (ledgeFrakjaw != nullptr) {
ledgeFrakjaw->Kill();
}
ActivityTimerStart(self, SpawnLowerFrakjawTimer, 1.0f, 1.0f);
GameMessages::SendNotifyClientObject(self->GetObjectID(), PlayCinematicNotification, 0, 0,
LWOOBJID_EMPTY, BottomFrakSpawn, UNASSIGNED_SYSTEM_ADDRESS);
} else if (timerName == SpawnLowerFrakjawTimer) {
auto spawners = dZoneManager::Instance()->GetSpawnersByName(LowerFrakjawSpawner);
if (!spawners.empty()) {
auto* spawner = spawners.front();
spawner->Activate();
}
} else if (timerName == SpawnRailTimer) {
GameMessages::SendNotifyClientObject(self->GetObjectID(), PlayCinematicNotification, 0, 0,
LWOOBJID_EMPTY, FireRailSpawn, UNASSIGNED_SYSTEM_ADDRESS);
auto spawners = dZoneManager::Instance()->GetSpawnersByName(FireRailSpawner);
if (!spawners.empty()) {
auto* spawner = spawners.front();
spawner->Activate();
}
} else if (timerName + TimerSplitChar == FrakjawSpawnInTimer) {
auto* lowerFrakjaw = EntityManager::Instance()->GetEntity(objectID);
if (lowerFrakjaw != nullptr) {
LowerFrakjawSummon(self, lowerFrakjaw);
}
} else if (timerName == WaveOverTimer) {
WaveOver(self);
} else if (timerName == FightOverTimer) {
FightOver(self);
}
}
// // // // // // // //
// Custom functions //
// // // // // // // //
void NjMonastryBossInstance::StartFight(Entity* self) {
if (self->GetVar<bool>(FightStartedVariable))
return;
self->SetVar<bool>(FightStartedVariable, true);
// Activate the frakjaw spawner
for (auto* spawner : dZoneManager::Instance()->GetSpawnersByName(LedgeFrakjawSpawner)) {
spawner->Activate();
}
}
void NjMonastryBossInstance::HandleLedgedFrakjawSpawned(Entity* self, Entity* ledgedFrakjaw) {
self->SetVar<LWOOBJID>(LedgeFrakjawVariable, ledgedFrakjaw->GetObjectID());
SummonWave(self, ledgedFrakjaw);
}
void NjMonastryBossInstance::HandleCounterWeightSpawned(Entity* self, Entity* counterWeight) {
auto* rebuildComponent = counterWeight->GetComponent<RebuildComponent>();
if (rebuildComponent != nullptr) {
rebuildComponent->AddRebuildStateCallback([this, self, counterWeight](eRebuildState state) {
switch (state) {
case REBUILD_BUILDING:
GameMessages::SendNotifyClientObject(self->GetObjectID(), PlayCinematicNotification,
0, 0, counterWeight->GetObjectID(),
BaseCounterweightQB + std::to_string(self->GetVar<uint32_t>(WaveNumberVariable)),
UNASSIGNED_SYSTEM_ADDRESS);
return;
case REBUILD_INCOMPLETE:
GameMessages::SendNotifyClientObject(self->GetObjectID(), EndCinematicNotification,
0, 0, LWOOBJID_EMPTY, "",
UNASSIGNED_SYSTEM_ADDRESS);
return;
case REBUILD_RESETTING:
ActivityTimerStart(self, SpawnCounterWeightTimer, 0.0f, 0.0f);
return;
case REBUILD_COMPLETED: {
// TODO: Move the platform?
// The counterweight is actually a moving platform and we should listen to the last waypoint event here
// 0.5f is a rough estimate of that path, though, and results in less needed logic
self->AddCallbackTimer(0.5f, [this, self, counterWeight]() {
if (counterWeight != nullptr) {
counterWeight->Kill();
}
auto* frakjaw = EntityManager::Instance()->GetEntity(self->GetVar<LWOOBJID>(LedgeFrakjawVariable));
if (frakjaw == nullptr) {
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"LedgeFrakjawDead", 0,
0, LWOOBJID_EMPTY, "", UNASSIGNED_SYSTEM_ADDRESS);
return;
}
auto* skillComponent = frakjaw->GetComponent<SkillComponent>();
if (skillComponent != nullptr) {
skillComponent->CalculateBehavior(1635, 39097, frakjaw->GetObjectID(), true, false);
}
GameMessages::SendPlayAnimation(frakjaw, StunnedAnimation);
GameMessages::SendPlayNDAudioEmitter(frakjaw, UNASSIGNED_SYSTEM_ADDRESS, CounterSmashAudio);
// Before wave 4 we should lower frakjaw from the ledge
if (self->GetVar<uint32_t>(WaveNumberVariable) == 3) {
LowerFrakjaw(self, frakjaw);
return;
}
ActivityTimerStart(self, SpawnNextWaveTimer, 2.0f, 2.0f);
});
}
default:
return;
}
});
}
}
void NjMonastryBossInstance::HandleLowerFrakjawSpawned(Entity* self, Entity* lowerFrakjaw) {
GameMessages::SendPlayAnimation(lowerFrakjaw, TeleportInAnimation);
self->SetVar<LWOOBJID>(LowerFrakjawVariable, lowerFrakjaw->GetObjectID());
auto* combatAI = lowerFrakjaw->GetComponent<BaseCombatAIComponent>();
if (combatAI != nullptr) {
combatAI->SetDisabled(true);
}
auto* destroyableComponent = lowerFrakjaw->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr) {
destroyableComponent->AddOnHitCallback([this, self, lowerFrakjaw](Entity* attacker) {
NjMonastryBossInstance::HandleLowerFrakjawHit(self, lowerFrakjaw, attacker);
});
}
lowerFrakjaw->AddDieCallback([this, self, lowerFrakjaw]() {
NjMonastryBossInstance::HandleLowerFrakjawDied(self, lowerFrakjaw);
});
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"LedgeFrakjawDead", 0, 0,
LWOOBJID_EMPTY, "", UNASSIGNED_SYSTEM_ADDRESS);
if (self->GetVar<bool>(LargeTeamVariable)) {
// Double frakjaws health for large teams
if (destroyableComponent != nullptr) {
const auto doubleHealth = destroyableComponent->GetHealth() * 2;
destroyableComponent->SetHealth(doubleHealth);
destroyableComponent->SetMaxHealth((float_t)doubleHealth);
}
ActivityTimerStart(self, FrakjawSpawnInTimer + std::to_string(lowerFrakjaw->GetObjectID()),
2.0f, 2.0f);
ActivityTimerStart(self, UnstunTimer + std::to_string(lowerFrakjaw->GetObjectID()),
7.0f, 7.0f);
} else {
ActivityTimerStart(self, UnstunTimer + std::to_string(lowerFrakjaw->GetObjectID()),
5.0f, 5.0f);
}
}
void NjMonastryBossInstance::HandleLowerFrakjawHit(Entity* self, Entity* lowerFrakjaw, Entity* attacker) {
auto* destroyableComponent = lowerFrakjaw->GetComponent<DestroyableComponent>();
if (destroyableComponent == nullptr)
return;
// Progress the fight to the last wave if frakjaw has less than 50% of his health left
if (destroyableComponent->GetHealth() <= (uint32_t)destroyableComponent->GetMaxHealth() / 2 && !self->GetVar<bool>(OnLastWaveVarbiale)) {
self->SetVar<bool>(OnLastWaveVarbiale, true);
// Stun frakjaw during the cinematic
auto* combatAI = lowerFrakjaw->GetComponent<BaseCombatAIComponent>();
if (combatAI != nullptr) {
combatAI->SetDisabled(true);
}
ActivityTimerStart(self, UnstunTimer + std::to_string(lowerFrakjaw->GetObjectID()), 5.0f, 5.0f);
const auto trashMobsAlive = self->GetVar<std::vector<LWOOBJID>>(TrashMobsAliveVariable);
std::vector<LWOOBJID> newTrashMobs = {};
for (const auto& trashMobID : trashMobsAlive) {
auto* trashMob = EntityManager::Instance()->GetEntity(trashMobID);
if (trashMob != nullptr) {
newTrashMobs.push_back(trashMobID);
// Stun all the enemies until the cinematic is over
auto* trashMobCombatAI = trashMob->GetComponent<BaseCombatAIComponent>();
if (trashMobCombatAI != nullptr) {
trashMobCombatAI->SetDisabled(true);
}
ActivityTimerStart(self, UnstunTimer + std::to_string(trashMobID), 5.0f, 5.0f);
}
}
self->SetVar<std::vector<LWOOBJID>>(TrashMobsAliveVariable, newTrashMobs);
LowerFrakjawSummon(self, lowerFrakjaw);
RemovePoison(self);
}
}
void NjMonastryBossInstance::HandleLowerFrakjawDied(Entity* self, Entity* lowerFrakjaw) {
ActivityTimerStart(self, FightOverTimer, 2.0f, 2.0f);
}
void NjMonastryBossInstance::HandleWaveEnemySpawned(Entity* self, Entity* waveEnemy) {
waveEnemy->AddDieCallback([this, self, waveEnemy]() {
NjMonastryBossInstance::HandleWaveEnemyDied(self, waveEnemy);
});
auto waveEnemies = self->GetVar<std::vector<LWOOBJID>>(TrashMobsAliveVariable);
waveEnemies.push_back(waveEnemy->GetObjectID());
self->SetVar<std::vector<LWOOBJID>>(TrashMobsAliveVariable, waveEnemies);
auto* combatAI = waveEnemy->GetComponent<BaseCombatAIComponent>();
if (combatAI != nullptr) {
combatAI->SetDisabled(true);
ActivityTimerStart(self, UnstunTimer + std::to_string(waveEnemy->GetObjectID()), 3.0f, 3.0f);
}
}
void NjMonastryBossInstance::HandleWaveEnemyDied(Entity* self, Entity* waveEnemy) {
auto waveEnemies = self->GetVar<std::vector<LWOOBJID>>(TrashMobsAliveVariable);
waveEnemies.erase(std::remove(waveEnemies.begin(), waveEnemies.end(), waveEnemy->GetObjectID()), waveEnemies.end());
self->SetVar<std::vector<LWOOBJID>>(TrashMobsAliveVariable, waveEnemies);
if (waveEnemies.empty()) {
ActivityTimerStart(self, WaveOverTimer, 2.0f, 2.0f);
}
}
void NjMonastryBossInstance::TeleportPlayer(Entity* player, uint32_t position) {
for (const auto* spawnPoint : EntityManager::Instance()->GetEntitiesInGroup("SpawnPoint" + std::to_string(position))) {
GameMessages::SendTeleport(player->GetObjectID(), spawnPoint->GetPosition(), spawnPoint->GetRotation(),
player->GetSystemAddress(), true);
}
}
void NjMonastryBossInstance::SummonWave(Entity* self, Entity* frakjaw) {
GameMessages::SendNotifyClientObject(self->GetObjectID(), PlayCinematicNotification, 0, 0, LWOOBJID_EMPTY,
LedgeFrakSummon, UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::SendPlayAnimation(frakjaw, SummonAnimation);
// Stop the music for the first, fourth and fifth wave
const auto wave = self->GetVar<uint32_t>(WaveNumberVariable);
if (wave >= 1 || wave < (m_Waves.size() - 1)) {
GameMessages::SendNotifyClientObject(self->GetObjectID(), StopMusicNotification, 0, 0,
LWOOBJID_EMPTY, AudioWaveAudio + std::to_string(wave - 1),
UNASSIGNED_SYSTEM_ADDRESS);
}
// After frakjaw moves down the music stays the same
if (wave < (m_Waves.size() - 1)) {
GameMessages::SendNotifyClientObject(self->GetObjectID(), StartMusicNotification, 0, 0,
LWOOBJID_EMPTY, AudioWaveAudio + std::to_string(wave),
UNASSIGNED_SYSTEM_ADDRESS);
}
ActivityTimerStart(self, SpawnWaveTimer, 4.0f, 4.0f);
}
void NjMonastryBossInstance::LowerFrakjawSummon(Entity* self, Entity* frakjaw) {
GameMessages::SendNotifyClientObject(self->GetObjectID(), PlayCinematicNotification, 0, 0,
LWOOBJID_EMPTY, BottomFrakSummon, UNASSIGNED_SYSTEM_ADDRESS);
ActivityTimerStart(self, SpawnWaveTimer, 2.0f, 2.0f);
GameMessages::SendPlayAnimation(frakjaw, SummonAnimation);
}
void NjMonastryBossInstance::RemovePoison(Entity* self) {
const auto& totalPlayer = self->GetVar<std::vector<LWOOBJID>>(TotalPlayersLoadedVariable);
for (const auto& playerID : totalPlayer) {
auto* player = EntityManager::Instance()->GetEntity(playerID);
if (player != nullptr) {
auto* buffComponent = player->GetComponent<BuffComponent>();
if (buffComponent != nullptr) {
buffComponent->RemoveBuff(PoisonBuff);
}
}
}
}
void NjMonastryBossInstance::LowerFrakjaw(Entity* self, Entity* frakjaw) {
GameMessages::SendPlayAnimation(frakjaw, TeleportOutAnimation);
ActivityTimerStart(self, LowerFrakjawCamTimer, 2.0f, 2.0f);
GameMessages::SendNotifyClientObject(frakjaw->GetObjectID(), StopMusicNotification, 0, 0,
LWOOBJID_EMPTY, AudioWaveAudio + std::to_string(m_Waves.size() - 3), UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::SendNotifyClientObject(frakjaw->GetObjectID(), StartMusicNotification, 0, 0,
LWOOBJID_EMPTY, AudioWaveAudio + std::to_string(m_Waves.size() - 2), UNASSIGNED_SYSTEM_ADDRESS);
}
void NjMonastryBossInstance::SpawnOnNetwork(Entity* self, const LOT& toSpawn, const uint32_t& numberToSpawn, const std::string& spawnerName) {
auto spawners = dZoneManager::Instance()->GetSpawnersByName(spawnerName);
if (spawners.empty() || numberToSpawn <= 0)
return;
auto* spawner = spawners.front();
// Spawn the lot N times
spawner->SetSpawnLot(toSpawn);
for (auto i = 0; i < numberToSpawn; i++)
spawner->Spawn({ spawner->m_Info.nodes.at(i % spawner->m_Info.nodes.size()) }, true);
}
void NjMonastryBossInstance::WaveOver(Entity* self) {
auto wave = self->GetVar<uint32_t>(WaveNumberVariable);
if (wave >= m_Waves.size() - 1)
return;
GameMessages::SendNotifyClientObject(self->GetObjectID(), PlayCinematicNotification, 0, 0,
LWOOBJID_EMPTY, BaseCounterweightSpawn + std::to_string(wave),
UNASSIGNED_SYSTEM_ADDRESS);
ActivityTimerStart(self, SpawnCounterWeightTimer, 1.5f, 1.5f);
RemovePoison(self);
}
void NjMonastryBossInstance::FightOver(Entity* self) {
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"GroundFrakjawDead", 0, 0,
LWOOBJID_EMPTY, "", UNASSIGNED_SYSTEM_ADDRESS);
// Remove all the enemies from the battlefield
for (auto i = 1; i < 5; i++) {
auto spawners = dZoneManager::Instance()->GetSpawnersByName(BaseEnemiesSpawner + std::to_string(i));
if (!spawners.empty()) {
auto* spawner = spawners.front();
spawner->Deactivate();
spawner->Reset();
}
}
RemovePoison(self);
ActivityTimerStart(self, SpawnRailTimer, 1.5f, 1.5f);
// Set the music to play the victory music
GameMessages::SendNotifyClientObject(self->GetObjectID(), StopMusicNotification, 0, 0,
LWOOBJID_EMPTY, AudioWaveAudio + std::to_string(m_Waves.size() - 2),
UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::SendNotifyClientObject(self->GetObjectID(), FlashMusicNotification, 0, 0,
LWOOBJID_EMPTY, "Monastery_Frakjaw_Battle_Win", UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::SendNotifyClientObject(self->GetObjectID(), PlayCinematicNotification, 0, 0,
LWOOBJID_EMPTY, TreasureChestSpawning, UNASSIGNED_SYSTEM_ADDRESS);
auto treasureChests = EntityManager::Instance()->GetEntitiesInGroup(ChestSpawnpointGroup);
for (auto* treasureChest : treasureChests) {
auto info = EntityInfo{};
info.lot = ChestLOT;
info.pos = treasureChest->GetPosition();
info.rot = treasureChest->GetRotation();
info.spawnerID = self->GetObjectID();
info.settings = {
new LDFData<LWOOBJID>(u"parent_tag", self->GetObjectID())
};
// Finally spawn a treasure chest at the correct spawn point
auto* chestObject = EntityManager::Instance()->CreateEntity(info);
EntityManager::Instance()->ConstructEntity(chestObject);
}
}

View File

@@ -0,0 +1,157 @@
#pragma once
#include "ActivityManager.h"
enum FrakjawEnemies {
BoneWolf = 16191,
BlackSmith = 14007,
Marksman = 14008,
Commando = 14009,
MadScientist = 16511
};
enum FrakjawLots : LOT {
ChestLOT = 16486,
LedgedFrakjawLOT = 16289,
LowerFrakjawLOT = 16048,
CounterWeightLOT = 16141
};
struct FrakjawWaveEnemy {
LOT lot;
uint32_t largeNumber;
uint32_t smallNumber;
};
class NjMonastryBossInstance : public ActivityManager {
public:
void OnStartup(Entity* self) override;
void OnPlayerLoaded(Entity* self, Entity* player) override;
void OnPlayerExit(Entity* self, Entity* player) override;
void OnActivityTimerDone(Entity* self, const std::string& name) override;
private:
void StartFight(Entity* self);
void WaveOver(Entity* self);
void FightOver(Entity* self);
void SummonWave(Entity* self, Entity* frakjaw);
void LowerFrakjaw(Entity* self, Entity* frakjaw);
void LowerFrakjawSummon(Entity* self, Entity* frakjaw);
void RemovePoison(Entity* self);
static void SpawnOnNetwork(Entity* self, const LOT& toSpawn, const uint32_t& numberToSpawn, const std::string& spawnerName);
static void TeleportPlayer(Entity* player, uint32_t position);
// Event handlers for anything spawned by the main spawner
void HandleLedgedFrakjawSpawned(Entity* self, Entity* ledgedFrakjaw);
void HandleCounterWeightSpawned(Entity* self, Entity* counterWeight);
void HandleLowerFrakjawSpawned(Entity* self, Entity* lowerFrakjaw);
void HandleWaveEnemySpawned(Entity* self, Entity* waveEnemy);
void HandleWaveEnemyDied(Entity* self, Entity* waveEnemy);
void HandleLowerFrakjawHit(Entity* self, Entity* lowerFrakjaw, Entity* attacker);
void HandleLowerFrakjawDied(Entity* self, Entity* lowerFrakjaw);
const std::vector<std::vector<FrakjawWaveEnemy>> m_Waves = {
// Wave 1
{
{ FrakjawEnemies::Marksman, 2, 1},
{ FrakjawEnemies::BlackSmith, 4, 3},
{ FrakjawEnemies::Commando, 2, 1},
{ FrakjawEnemies::MadScientist, 1, 0},
},
// Wave 2
{
{ FrakjawEnemies::BoneWolf, 1, 0},
{ FrakjawEnemies::BlackSmith, 2, 2},
{ FrakjawEnemies::Marksman, 2, 1},
{ FrakjawEnemies::MadScientist, 1, 1},
},
// Wave 3
{
{ FrakjawEnemies::BoneWolf, 2, 1},
{ FrakjawEnemies::Marksman, 2, 1},
{ FrakjawEnemies::Commando, 2, 2},
{ FrakjawEnemies::MadScientist, 1, 0},
},
// Wave 4
{
{ FrakjawEnemies::BlackSmith, 2, 2},
{ FrakjawEnemies::BoneWolf, 1, 1},
{ FrakjawEnemies::Commando, 3, 1},
{ FrakjawEnemies::Marksman, 2, 0},
},
// Wave 5
{
{ FrakjawEnemies::MadScientist, 1, 0},
{ FrakjawEnemies::BoneWolf, 2, 0},
{ FrakjawEnemies::Commando, 3, 0},
{ FrakjawEnemies::Marksman, 2, 0},
}
};
const int32_t PoisonBuff = 60;
// Variables
const std::u16string TotalPlayersLoadedVariable = u"TotalPlayersLoaded";
const std::u16string LargeTeamVariable = u"LargeTeam";
const std::u16string FightStartedVariable = u"FightStarted";
const std::u16string LedgeFrakjawVariable = u"LedgeFrakjaw";
const std::u16string LowerFrakjawVariable = u"LowerFrakjaw";
const std::u16string WaveNumberVariable = u"WaveNumber";
const std::u16string OnLastWaveVarbiale = u"OnLastWave";
const std::u16string TrashMobsAliveVariable = u"TrashMobsAlive";
const std::u16string TotalAliveInWaveVariable = u"TotalAliveInWave";
// Timers
const char TimerSplitChar = '+';
const std::string WaitingForPlayersTimer = "WaitingForPlayers";
const std::string SpawnWaveTimer = "SpawnWave";
const std::string SpawnNextWaveTimer = "SpawnNextWave";
const std::string UnstunTimer = "Unstun+";
const std::string FrakjawSpawnInTimer = "LowerFrakjawSpawnIn+";
const std::string WaveOverTimer = "WaveOverTimer";
const std::string FightOverTimer = "FightOver";
const std::string LowerFrakjawCamTimer = "StartLowerFrakjawCam";
const std::string SpawnCounterWeightTimer = "SpawnQB";
const std::string SpawnRailTimer = "SpawnRailQB";
const std::string SpawnLowerFrakjawTimer = "SpawnLowerFrakjaw";
// Groups
const std::string ChestSpawnpointGroup = "ChestSpawnPoint";
// Spawner network names
const std::string LedgeFrakjawSpawner = "LedgeFrakjaw";
const std::string LowerFrakjawSpawner = "LowerFrakjaw";
const std::string BaseEnemiesSpawner = "EnemySpawnPoints_";
const std::string CounterweightSpawner = "Counterweights";
const std::string FireRailSpawner = "FireRailActivatorQB";
const std::string ExtraRocks = "ExtraRocks";
// Cinematics
const std::string LedgeFrakSummon = "FrakjawSummoning";
const std::string BaseCounterweightQB = "CounterweightQB";
const std::string BaseCounterweightSpawn = "CWQBSpawn";
const std::string BottomFrakSummon = "BottomFrakjawSummoning";
const std::string BottomFrakSpawn = "BottomFrakjawSpawning";
const std::string TreasureChestSpawning = "TreasureChestSpawning";
const std::string FireRailSpawn = "RailQBSpawn";
// Notifications
const std::u16string StopMusicNotification = u"StopMusic";
const std::u16string StartMusicNotification = u"StartMusic";
const std::u16string FlashMusicNotification = u"FlashMusic";
const std::u16string PlayCinematicNotification = u"PlayCinematic";
const std::u16string EndCinematicNotification = u"EndCinematic";
// Animations
const std::u16string SummonAnimation = u"summon";
const std::u16string TeleportOutAnimation = u"teleport-out";
const std::u16string TeleportInAnimation = u"teleport-in";
const std::u16string StunnedAnimation = u"stunned";
// Audio cues
const std::string AudioWaveAudio = "Monastery_Frakjaw_Battle_";
const std::string BattleOverAudio = "Monastery_Frakjaw_Battle_Win";
const std::string CounterSmashAudio = "{d76d7b9d-9dc2-4e52-a315-69b25ef521ca}";
};