#include <algorithm>
#include "RebuildComponent.h"
#include "NjMonastryBossInstance.h"
#include "DestroyableComponent.h"
#include "EntityManager.h"
#include "GameMessages.h"
#include "dZoneManager.h"
#include "GameMessages.h"
#include "BaseCombatAIComponent.h"
#include "BuffComponent.h"
#include "SkillComponent.h"
#include "TeamManager.h"

// // // // // // //
// 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 and charge for joining
    UpdatePlayer(self, player->GetObjectID());
    TakeActivityCost(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());
    }

    // Track the player ID
    auto totalPlayersLoaded = self->GetVar<std::vector<LWOOBJID>>(TotalPlayersLoadedVariable);
    if (totalPlayersLoaded.empty() || std::find(totalPlayersLoaded.begin(), totalPlayersLoaded.end(), player->GetObjectID()) != totalPlayersLoaded.end()) {
        totalPlayersLoaded.push_back(player->GetObjectID());
    }

    // Properly position the player
    self->SetVar<std::vector<LWOOBJID>>(TotalPlayersLoadedVariable, totalPlayersLoaded);
    TeleportPlayer(player, totalPlayersLoaded.size());

    // Large teams face a tougher challenge
    if (totalPlayersLoaded.size() > 2)
        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);
    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::stol(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);
    }
}
