mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-13 06:21:28 +00:00
[Zone] Zone State Automated Testing and Improvements (#4808)
* [Zone] Zone State Automated Testing and Improvements * Spawn condition * Update zone.cpp * Remove redundant logic * Update zone_state.cpp * TestZLocationDrift * Protect NPC resumed NPC's from being able to die
This commit is contained in:
parent
c8a7066d0e
commit
b9cfdea76c
@ -196,3 +196,25 @@ const uint32 Timer::SetCurrentTime()
|
|||||||
return current_time;
|
return current_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uint32 Timer::RollForward(uint32 seconds)
|
||||||
|
{
|
||||||
|
struct timeval read_time{};
|
||||||
|
uint32 this_time;
|
||||||
|
|
||||||
|
gettimeofday(&read_time, nullptr);
|
||||||
|
this_time = read_time.tv_sec * 1000 + read_time.tv_usec / 1000;
|
||||||
|
|
||||||
|
if (last_time == 0) {
|
||||||
|
current_time = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
current_time += this_time - last_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_time = this_time;
|
||||||
|
|
||||||
|
// Roll forward the specified number of seconds (converted to milliseconds)
|
||||||
|
current_time += seconds * 1000;
|
||||||
|
|
||||||
|
return current_time;
|
||||||
|
}
|
||||||
|
|||||||
@ -51,6 +51,7 @@ public:
|
|||||||
inline uint32 GetDuration() { return(timer_time); }
|
inline uint32 GetDuration() { return(timer_time); }
|
||||||
|
|
||||||
static const uint32 SetCurrentTime();
|
static const uint32 SetCurrentTime();
|
||||||
|
static const uint32 RollForward(uint32 seconds);
|
||||||
static const uint32 GetCurrentTime();
|
static const uint32 GetCurrentTime();
|
||||||
static const uint32 GetTimeSeconds();
|
static const uint32 GetTimeSeconds();
|
||||||
|
|
||||||
|
|||||||
@ -57,6 +57,7 @@ echo "# Running NPC hand-in tests"
|
|||||||
./bin/zone tests:npc-handins 2>&1 | tee test_output.log
|
./bin/zone tests:npc-handins 2>&1 | tee test_output.log
|
||||||
./bin/zone tests:npc-handins-multiquest 2>&1 | tee -a test_output.log
|
./bin/zone tests:npc-handins-multiquest 2>&1 | tee -a test_output.log
|
||||||
./bin/zone tests:databuckets 2>&1 | tee -a test_output.log
|
./bin/zone tests:databuckets 2>&1 | tee -a test_output.log
|
||||||
|
./bin/zone tests:zone-state 2>&1 | tee -a test_output.log
|
||||||
|
|
||||||
if grep -E -q "QueryErr|Error|FAILED" test_output.log; then
|
if grep -E -q "QueryErr|Error|FAILED" test_output.log; then
|
||||||
echo "Error found in test output! Failing build."
|
echo "Error found in test output! Failing build."
|
||||||
|
|||||||
@ -173,6 +173,7 @@ SET(zone_sources
|
|||||||
zone_event_scheduler.cpp
|
zone_event_scheduler.cpp
|
||||||
zone_npc_factions.cpp
|
zone_npc_factions.cpp
|
||||||
zone_reload.cpp
|
zone_reload.cpp
|
||||||
|
zone_save_state.cpp
|
||||||
zoning.cpp
|
zoning.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -292,6 +293,7 @@ SET(zone_headers
|
|||||||
zonedump.h
|
zonedump.h
|
||||||
zone_cli.h
|
zone_cli.h
|
||||||
zone_reload.h
|
zone_reload.h
|
||||||
|
zone_save_state.h
|
||||||
zone_cli.cpp)
|
zone_cli.cpp)
|
||||||
|
|
||||||
ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers})
|
ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers})
|
||||||
|
|||||||
@ -2507,6 +2507,12 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_resumed_from_zone_suspend && !IsQueuedForCorpse()) {
|
||||||
|
LogInfo("NPC [{}] is resumed from zone suspend, cannot kill until zone resume is complete.", GetCleanName());
|
||||||
|
SetHP(0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (IsMultiQuestEnabled()) {
|
if (IsMultiQuestEnabled()) {
|
||||||
for (auto &i: m_hand_in.items) {
|
for (auto &i: m_hand_in.items) {
|
||||||
if (i.is_multiquest_item && i.item->GetItem()->NoDrop != 0) {
|
if (i.is_multiquest_item && i.item->GetItem()->NoDrop != 0) {
|
||||||
@ -2627,7 +2633,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
|
|||||||
bool pet_owner_is_client = give_exp->IsPet() && owner->IsClient();
|
bool pet_owner_is_client = give_exp->IsPet() && owner->IsClient();
|
||||||
bool pet_owner_is_bot = give_exp->IsPet() && owner->IsBot();
|
bool pet_owner_is_bot = give_exp->IsPet() && owner->IsBot();
|
||||||
bool owner_is_client = owner->IsClient();
|
bool owner_is_client = owner->IsClient();
|
||||||
|
|
||||||
bool is_in_same_group_or_raid = (
|
bool is_in_same_group_or_raid = (
|
||||||
pet_owner_is_client ||
|
pet_owner_is_client ||
|
||||||
(pet_owner_is_bot && owner->IsInGroupOrRaid(ulimate_owner)) ||
|
(pet_owner_is_bot && owner->IsInGroupOrRaid(ulimate_owner)) ||
|
||||||
|
|||||||
57
zone/cli/tests/_test_util.cpp
Normal file
57
zone/cli/tests/_test_util.cpp
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#include "../../zone.h"
|
||||||
|
|
||||||
|
inline void RunTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
||||||
|
{
|
||||||
|
if (expected == actual) {
|
||||||
|
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||||
|
} else {
|
||||||
|
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||||
|
std::cerr << " 📌 Expected: " << expected << "\n";
|
||||||
|
std::cerr << " ❌ Got: " << actual << "\n";
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void RunTest(const std::string &test_name, bool expected, bool actual)
|
||||||
|
{
|
||||||
|
if (expected == actual) {
|
||||||
|
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||||
|
std::cerr << " 📌 Expected: " << (expected ? "true" : "false") << "\n";
|
||||||
|
std::cerr << " ❌ Got: " << (actual ? "true" : "false") << "\n";
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void RunTest(const std::string &test_name, int expected, int actual)
|
||||||
|
{
|
||||||
|
if (expected == actual) {
|
||||||
|
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||||
|
std::cerr << " 📌 Expected: " << expected << "\n";
|
||||||
|
std::cerr << " ❌ Got: " << actual << "\n";
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern Zone *zone;
|
||||||
|
|
||||||
|
inline void SetupZone(std::string zone_short_name, uint32 instance_id = 0) {
|
||||||
|
LogSys.SilenceConsoleLogging();
|
||||||
|
|
||||||
|
LogSys.log_settings[Logs::ZoneState].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||||
|
LogSys.log_settings[Logs::Info].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||||
|
LogSys.log_settings[Logs::Spawns].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||||
|
|
||||||
|
// boot shell zone for testing
|
||||||
|
Zone::Bootup(ZoneID(zone_short_name), 0, false);
|
||||||
|
zone->StopShutdownTimer();
|
||||||
|
entity_list.Process();
|
||||||
|
entity_list.MobProcess();
|
||||||
|
|
||||||
|
LogSys.EnableConsoleLogging();
|
||||||
|
}
|
||||||
@ -1,25 +1,13 @@
|
|||||||
#include "../../common/http/httplib.h"
|
#include "../../common/http/httplib.h"
|
||||||
#include "../../common/eqemu_logsys.h"
|
#include "../../common/eqemu_logsys.h"
|
||||||
#include "../../common/platform.h"
|
#include "../../common/platform.h"
|
||||||
#include "../zone.h"
|
#include "../../zone.h"
|
||||||
#include "../client.h"
|
#include "../../client.h"
|
||||||
#include "../../common/net/eqstream.h"
|
#include "../../common/net/eqstream.h"
|
||||||
|
|
||||||
extern Zone *zone;
|
extern Zone *zone;
|
||||||
|
|
||||||
void RunTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
void ZoneCLI::TestDataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||||
{
|
|
||||||
if (expected == actual) {
|
|
||||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
|
||||||
} else {
|
|
||||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
|
||||||
std::cerr << " 📌 Expected: " << expected << "\n";
|
|
||||||
std::cerr << " ❌ Got: " << actual << "\n";
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZoneCLI::DataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
|
|
||||||
{
|
{
|
||||||
if (cmd[{"-h", "--help"}]) {
|
if (cmd[{"-h", "--help"}]) {
|
||||||
return;
|
return;
|
||||||
@ -1,8 +1,8 @@
|
|||||||
#include "../../common/http/httplib.h"
|
#include "../../common/http/httplib.h"
|
||||||
#include "../../common/eqemu_logsys.h"
|
#include "../../common/eqemu_logsys.h"
|
||||||
#include "../../common/platform.h"
|
#include "../../common/platform.h"
|
||||||
#include "../zone.h"
|
#include "../../zone.h"
|
||||||
#include "../client.h"
|
#include "../../client.h"
|
||||||
#include "../../common/net/eqstream.h"
|
#include "../../common/net/eqstream.h"
|
||||||
#include "../../common/json/json.hpp"
|
#include "../../common/json/json.hpp"
|
||||||
|
|
||||||
@ -36,19 +36,6 @@ struct TestCase {
|
|||||||
bool handin_check_result;
|
bool handin_check_result;
|
||||||
};
|
};
|
||||||
|
|
||||||
void RunTest(const std::string &test_name, bool expected, bool actual)
|
|
||||||
{
|
|
||||||
if (expected == actual) {
|
|
||||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
|
||||||
std::cerr << " 📌 Expected: " << (expected ? "true" : "false") << "\n";
|
|
||||||
std::cerr << " ❌ Got: " << (actual ? "true" : "false") << "\n";
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RunSerializedTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
void RunSerializedTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
||||||
{
|
{
|
||||||
if (expected == actual) {
|
if (expected == actual) {
|
||||||
@ -75,7 +62,7 @@ std::string SerializeHandin(const std::map<std::string, uint32> &items, const Ha
|
|||||||
return j.dump();
|
return j.dump();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
|
void ZoneCLI::TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||||
{
|
{
|
||||||
if (cmd[{"-h", "--help"}]) {
|
if (cmd[{"-h", "--help"}]) {
|
||||||
return;
|
return;
|
||||||
@ -1,13 +1,13 @@
|
|||||||
#include "../../common/http/httplib.h"
|
#include "../../common/http/httplib.h"
|
||||||
#include "../../common/eqemu_logsys.h"
|
#include "../../common/eqemu_logsys.h"
|
||||||
#include "../../common/platform.h"
|
#include "../../common/platform.h"
|
||||||
#include "../zone.h"
|
#include "../../zone.h"
|
||||||
#include "../client.h"
|
#include "../../client.h"
|
||||||
#include "../../common/net/eqstream.h"
|
#include "../../common/net/eqstream.h"
|
||||||
|
|
||||||
extern Zone *zone;
|
extern Zone *zone;
|
||||||
|
|
||||||
void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description)
|
void ZoneCLI::TestNpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||||
{
|
{
|
||||||
if (cmd[{"-h", "--help"}]) {
|
if (cmd[{"-h", "--help"}]) {
|
||||||
return;
|
return;
|
||||||
1094
zone/cli/tests/zone_state.cpp
Normal file
1094
zone/cli/tests/zone_state.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -5991,3 +5991,14 @@ void EntityList::SendMerchantInventory(Mob* m, int32 slot_id, bool is_delete)
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityList::RestoreCorpse(NPC *npc, uint32_t decay_time)
|
||||||
|
{
|
||||||
|
uint16 corpse_id = npc->GetID();
|
||||||
|
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
||||||
|
auto c = entity_list.GetCorpseByID(corpse_id);
|
||||||
|
if (c) {
|
||||||
|
c->UnLock();
|
||||||
|
c->SetDecayTimer(decay_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -580,6 +580,7 @@ public:
|
|||||||
|
|
||||||
void SendMerchantEnd(Mob* merchant);
|
void SendMerchantEnd(Mob* merchant);
|
||||||
void SendMerchantInventory(Mob* m, int32 slot_id = -1, bool is_delete = false);
|
void SendMerchantInventory(Mob* m, int32 slot_id = -1, bool is_delete = false);
|
||||||
|
void RestoreCorpse(NPC* npc, uint32_t decay_time);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class Zone;
|
friend class Zone;
|
||||||
|
|||||||
@ -1125,7 +1125,7 @@ public:
|
|||||||
|
|
||||||
virtual void SetAttackTimer();
|
virtual void SetAttackTimer();
|
||||||
inline void SetInvul(bool invul) { invulnerable=invul; }
|
inline void SetInvul(bool invul) { invulnerable=invul; }
|
||||||
inline bool GetInvul(void) { return invulnerable; }
|
inline bool GetInvul() { return invulnerable; }
|
||||||
void SetExtraHaste(int haste, bool need_to_save = true);
|
void SetExtraHaste(int haste, bool need_to_save = true);
|
||||||
inline int GetExtraHaste() { return extra_haste; }
|
inline int GetExtraHaste() { return extra_haste; }
|
||||||
virtual int GetHaste();
|
virtual int GetHaste();
|
||||||
|
|||||||
35
zone/npc.cpp
35
zone/npc.cpp
@ -132,8 +132,6 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
|||||||
),
|
),
|
||||||
attacked_timer(CombatEventTimer_expire),
|
attacked_timer(CombatEventTimer_expire),
|
||||||
swarm_timer(100),
|
swarm_timer(100),
|
||||||
m_corpse_queue_timer(1000),
|
|
||||||
m_corpse_queue_shutoff_timer(30000),
|
|
||||||
m_resumed_from_zone_suspend_shutoff_timer(10000),
|
m_resumed_from_zone_suspend_shutoff_timer(10000),
|
||||||
classattack_timer(1000),
|
classattack_timer(1000),
|
||||||
monkattack_timer(1000),
|
monkattack_timer(1000),
|
||||||
@ -624,28 +622,6 @@ bool NPC::Process()
|
|||||||
|
|
||||||
// zone state corpse creation timer
|
// zone state corpse creation timer
|
||||||
if (RuleB(Zone, StateSavingOnShutdown)) {
|
if (RuleB(Zone, StateSavingOnShutdown)) {
|
||||||
// creates a corpse if the NPC is queued for corpse creation
|
|
||||||
if (m_corpse_queue_timer.Check()) {
|
|
||||||
if (IsQueuedForCorpse()) {
|
|
||||||
auto decay_timer = m_corpse_decay_time;
|
|
||||||
uint16 corpse_id = GetID();
|
|
||||||
Death(this, GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
|
||||||
auto c = entity_list.GetCorpseByID(corpse_id);
|
|
||||||
if (c) {
|
|
||||||
c->UnLock();
|
|
||||||
c->SetDecayTimer(decay_timer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_corpse_queue_timer.Disable();
|
|
||||||
m_corpse_queue_shutoff_timer.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// shuts off the corpse queue timer if it is still running
|
|
||||||
if (m_corpse_queue_shutoff_timer.Check()) {
|
|
||||||
m_corpse_queue_timer.Disable();
|
|
||||||
m_corpse_queue_shutoff_timer.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// shuts off the temporary spawn protected state of the NPC
|
// shuts off the temporary spawn protected state of the NPC
|
||||||
if (m_resumed_from_zone_suspend_shutoff_timer.Check()) {
|
if (m_resumed_from_zone_suspend_shutoff_timer.Check()) {
|
||||||
m_resumed_from_zone_suspend_shutoff_timer.Disable();
|
m_resumed_from_zone_suspend_shutoff_timer.Disable();
|
||||||
@ -654,17 +630,6 @@ bool NPC::Process()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tic_timer.Check()) {
|
if (tic_timer.Check()) {
|
||||||
if (RuleB(Zone, StateSavingOnShutdown) && IsQueuedForCorpse()) {
|
|
||||||
auto decay_timer = m_corpse_decay_time;
|
|
||||||
uint16 corpse_id = GetID();
|
|
||||||
Death(this, GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
|
||||||
auto c = entity_list.GetCorpseByID(corpse_id);
|
|
||||||
if (c) {
|
|
||||||
c->UnLock();
|
|
||||||
c->SetDecayTimer(decay_timer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_TICK)) {
|
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_TICK)) {
|
||||||
parse->EventNPC(EVENT_TICK, this, nullptr, "", 0);
|
parse->EventNPC(EVENT_TICK, this, nullptr, "", 0);
|
||||||
}
|
}
|
||||||
|
|||||||
16
zone/npc.h
16
zone/npc.h
@ -603,10 +603,9 @@ public:
|
|||||||
|
|
||||||
// zone state save
|
// zone state save
|
||||||
inline void SetQueuedToCorpse() { m_queued_for_corpse = true; }
|
inline void SetQueuedToCorpse() { m_queued_for_corpse = true; }
|
||||||
inline bool IsQueuedForCorpse() { return m_queued_for_corpse; }
|
inline bool IsQueuedForCorpse() const { return m_queued_for_corpse; }
|
||||||
inline uint32_t SetCorpseDecayTime(uint32_t decay_time) { return m_corpse_decay_time = decay_time; }
|
|
||||||
inline void SetResumedFromZoneSuspend(bool state = true) { m_resumed_from_zone_suspend = state; }
|
inline void SetResumedFromZoneSuspend(bool state = true) { m_resumed_from_zone_suspend = state; }
|
||||||
inline bool IsResumedFromZoneSuspend() { return m_resumed_from_zone_suspend; }
|
inline bool IsResumedFromZoneSuspend() const { return m_resumed_from_zone_suspend; }
|
||||||
|
|
||||||
inline void LoadBuffsFromState(std::vector<Buffs_Struct> in_buffs) {
|
inline void LoadBuffsFromState(std::vector<Buffs_Struct> in_buffs) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@ -658,15 +657,12 @@ protected:
|
|||||||
LootItems m_loot_items;
|
LootItems m_loot_items;
|
||||||
|
|
||||||
// zone state
|
// zone state
|
||||||
bool m_resumed_from_zone_suspend = false;
|
bool m_resumed_from_zone_suspend = false;
|
||||||
bool m_queued_for_corpse = false; // this is to check for corpse creation on zone state restore
|
bool m_queued_for_corpse = false; // this is to check for corpse creation on zone state restore
|
||||||
uint32_t m_corpse_decay_time = 0; // decay time set on zone state restore
|
|
||||||
Timer m_corpse_queue_timer = {}; // this is to check for corpse creation on zone state restore
|
|
||||||
Timer m_corpse_queue_shutoff_timer = {};
|
|
||||||
|
|
||||||
// this is a 30-second timer that protects a NPC from having double assignment of loot
|
// this is a timer that protects a NPC from having double assignment of loot
|
||||||
// this is to prevent a player from killing a NPC and then zoning out and back in to get loot again
|
// this is to prevent a player from killing a NPC and then zoning out and back in to get loot again
|
||||||
// if loot was to be assigned via script again, this protects double assignment for 30 seconds
|
// if loot was to be assigned via script again, this protects double assignment for a short time
|
||||||
Timer m_resumed_from_zone_suspend_shutoff_timer = {};
|
Timer m_resumed_from_zone_suspend_shutoff_timer = {};
|
||||||
|
|
||||||
std::list<NpcFactionEntriesRepository::NpcFactionEntries> faction_list;
|
std::list<NpcFactionEntriesRepository::NpcFactionEntries> faction_list;
|
||||||
|
|||||||
@ -191,16 +191,20 @@ bool Spawn2::Process() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16 condition_value=1;
|
uint16 condition_value = 1;
|
||||||
|
|
||||||
if (condition_id > 0) {
|
if (condition_id > 0) {
|
||||||
condition_value = zone->spawn_conditions.GetCondition(zone->GetShortName(), zone->GetInstanceID(), condition_id);
|
condition_value = zone->spawn_conditions.GetCondition(
|
||||||
|
zone->GetShortName(),
|
||||||
|
zone->GetInstanceID(),
|
||||||
|
condition_id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//have the spawn group pick an NPC for us
|
//have the spawn group pick an NPC for us
|
||||||
uint32 npcid = 0;
|
uint32 npcid = 0;
|
||||||
if (RuleB(Zone, StateSavingOnShutdown) && currentnpcid && currentnpcid > 0) {
|
if (m_resumed_npc_id > 0) {
|
||||||
npcid = currentnpcid;
|
npcid = m_resumed_npc_id;
|
||||||
|
m_resumed_npc_id = 0;
|
||||||
} else {
|
} else {
|
||||||
npcid = spawn_group->GetNPCType(condition_value);
|
npcid = spawn_group->GetNPCType(condition_value);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,6 +78,7 @@ public:
|
|||||||
inline bool IsResumedFromZoneSuspend() const { return m_resumed_from_zone_suspend; }
|
inline bool IsResumedFromZoneSuspend() const { return m_resumed_from_zone_suspend; }
|
||||||
inline void SetResumedFromZoneSuspend(bool resumed) { m_resumed_from_zone_suspend = resumed; }
|
inline void SetResumedFromZoneSuspend(bool resumed) { m_resumed_from_zone_suspend = resumed; }
|
||||||
inline void SetEntityVariables(std::map<std::string, std::string> vars) { m_entity_variables = vars; }
|
inline void SetEntityVariables(std::map<std::string, std::string> vars) { m_entity_variables = vars; }
|
||||||
|
inline void SetResumedNPCID(uint32 npc_id) { m_resumed_npc_id = npc_id; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class Zone;
|
friend class Zone;
|
||||||
@ -105,6 +106,7 @@ private:
|
|||||||
bool IsDespawned;
|
bool IsDespawned;
|
||||||
uint32 killcount;
|
uint32 killcount;
|
||||||
bool m_resumed_from_zone_suspend = false;
|
bool m_resumed_from_zone_suspend = false;
|
||||||
|
uint32 m_resumed_npc_id = 0;
|
||||||
std::map<std::string, std::string> m_entity_variables = {};
|
std::map<std::string, std::string> m_entity_variables = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -887,10 +887,7 @@ void Zone::Shutdown(bool quiet)
|
|||||||
c.second->WorldKick();
|
c.second->WorldKick();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool does_zone_have_entities =
|
if (RuleB(Zone, StateSavingOnShutdown) && zone && zone->IsLoaded()) {
|
||||||
zone && zone->IsLoaded() &&
|
|
||||||
(!entity_list.GetNPCList().empty() || !entity_list.GetCorpseList().empty());
|
|
||||||
if (RuleB(Zone, StateSavingOnShutdown) && does_zone_have_entities) {
|
|
||||||
SaveZoneState();
|
SaveZoneState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1537,7 +1534,6 @@ bool Zone::Process() {
|
|||||||
spawn_conditions.Process();
|
spawn_conditions.Process();
|
||||||
|
|
||||||
if (spawn2_timer.Check()) {
|
if (spawn2_timer.Check()) {
|
||||||
|
|
||||||
LinkedListIterator<Spawn2 *> iterator(spawn2_list);
|
LinkedListIterator<Spawn2 *> iterator(spawn2_list);
|
||||||
|
|
||||||
EQ::InventoryProfile::CleanDirty();
|
EQ::InventoryProfile::CleanDirty();
|
||||||
@ -3295,5 +3291,4 @@ void Zone::ReloadMaps()
|
|||||||
pathing = IPathfinder::Load(map_name);
|
pathing = IPathfinder::Load(map_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "zone_save_state.cpp"
|
|
||||||
#include "zone_loot.cpp"
|
#include "zone_loot.cpp"
|
||||||
|
|||||||
@ -29,17 +29,23 @@ void ZoneCLI::CommandHandler(int argc, char **argv)
|
|||||||
auto function_map = EQEmuCommand::function_map;
|
auto function_map = EQEmuCommand::function_map;
|
||||||
|
|
||||||
// Register commands
|
// Register commands
|
||||||
function_map["benchmark:databuckets"] = &ZoneCLI::BenchmarkDatabuckets;
|
function_map["benchmark:databuckets"] = &ZoneCLI::BenchmarkDatabuckets;
|
||||||
function_map["sidecar:serve-http"] = &ZoneCLI::SidecarServeHttp;
|
function_map["sidecar:serve-http"] = &ZoneCLI::SidecarServeHttp;
|
||||||
function_map["tests:databuckets"] = &ZoneCLI::DataBuckets;
|
function_map["tests:databuckets"] = &ZoneCLI::TestDataBuckets;
|
||||||
function_map["tests:npc-handins"] = &ZoneCLI::NpcHandins;
|
function_map["tests:npc-handins"] = &ZoneCLI::TestNpcHandins;
|
||||||
function_map["tests:npc-handins-multiquest"] = &ZoneCLI::NpcHandinsMultiQuest;
|
function_map["tests:npc-handins-multiquest"] = &ZoneCLI::TestNpcHandinsMultiQuest;
|
||||||
|
function_map["tests:zone-state"] = &ZoneCLI::TestZoneState;
|
||||||
|
|
||||||
EQEmuCommand::HandleMenu(function_map, cmd, argc, argv);
|
EQEmuCommand::HandleMenu(function_map, cmd, argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "cli/databuckets.cpp"
|
// cli
|
||||||
#include "cli/benchmark_databuckets.cpp"
|
#include "cli/benchmark_databuckets.cpp"
|
||||||
#include "cli/sidecar_serve_http.cpp"
|
#include "cli/sidecar_serve_http.cpp"
|
||||||
#include "cli/npc_handins.cpp"
|
|
||||||
#include "cli/npc_handins_multiquest.cpp"
|
// tests
|
||||||
|
#include "cli/tests/_test_util.cpp"
|
||||||
|
#include "cli/tests/databuckets.cpp"
|
||||||
|
#include "cli/tests/npc_handins.cpp"
|
||||||
|
#include "cli/tests/npc_handins_multiquest.cpp"
|
||||||
|
#include "cli/tests/zone_state.cpp"
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#ifndef EQEMU_ZONE_CLI_H
|
#ifndef EQEMU_ZONE_CLI_H
|
||||||
#define EQEMU_ZONE_CLI_H
|
#define EQEMU_ZONE_CLI_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
#include "../common/cli/argh.h"
|
#include "../common/cli/argh.h"
|
||||||
|
|
||||||
class ZoneCLI {
|
class ZoneCLI {
|
||||||
@ -11,10 +12,10 @@ public:
|
|||||||
static bool RanConsoleCommand(int argc, char **argv);
|
static bool RanConsoleCommand(int argc, char **argv);
|
||||||
static bool RanSidecarCommand(int argc, char **argv);
|
static bool RanSidecarCommand(int argc, char **argv);
|
||||||
static bool RanTestCommand(int argc, char **argv);
|
static bool RanTestCommand(int argc, char **argv);
|
||||||
static void DataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description);
|
static void TestDataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||||
static void NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description);
|
static void TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||||
static void NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description);
|
static void TestNpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||||
|
static void TestZoneState(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif //EQEMU_ZONE_CLI_H
|
#endif //EQEMU_ZONE_CLI_H
|
||||||
|
|||||||
@ -368,18 +368,22 @@ void Zone::LoadLootDrops(const std::vector<uint32> in_lootdrop_ids)
|
|||||||
m_lootdrops.emplace_back(e);
|
m_lootdrops.emplace_back(e);
|
||||||
|
|
||||||
// add lootdrop entries
|
// add lootdrop entries
|
||||||
for (const auto &f: lootdrop_entries) {
|
// add lootdrop entries
|
||||||
if (e.id == f.lootdrop_id) {
|
for (const auto &h: lootdrop_entries) {
|
||||||
|
if (e.id == h.lootdrop_id) {
|
||||||
|
|
||||||
// check if lootdrop entry already exists in memory
|
// check if lootdrop entry already exists in memory
|
||||||
has_entry = false;
|
has_entry = false;
|
||||||
for (const auto &g: m_lootdrop_entries) {
|
for (const auto &i: m_lootdrop_entries) {
|
||||||
if (f.lootdrop_id == g.lootdrop_id && f.item_id == g.item_id && f.multiplier == g.multiplier) {
|
if (h.lootdrop_id == i.lootdrop_id && h.item_id == i.item_id) {
|
||||||
has_entry = true;
|
has_entry = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!has_entry) {
|
||||||
|
m_lootdrop_entries.emplace_back(h);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,46 +4,7 @@
|
|||||||
#include "npc.h"
|
#include "npc.h"
|
||||||
#include "corpse.h"
|
#include "corpse.h"
|
||||||
#include "zone.h"
|
#include "zone.h"
|
||||||
#include "../common/repositories/zone_state_spawns_repository.h"
|
#include "zone_save_state.h"
|
||||||
#include "../common/repositories/spawn2_disabled_repository.h"
|
|
||||||
|
|
||||||
struct LootEntryStateData {
|
|
||||||
uint32 item_id;
|
|
||||||
uint32_t lootdrop_id;
|
|
||||||
uint16 charges = 0; // used in dynamically added loot (AddItem)
|
|
||||||
|
|
||||||
// cereal
|
|
||||||
template<class Archive>
|
|
||||||
void serialize(Archive &ar)
|
|
||||||
{
|
|
||||||
ar(
|
|
||||||
CEREAL_NVP(item_id),
|
|
||||||
CEREAL_NVP(lootdrop_id),
|
|
||||||
CEREAL_NVP(charges)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LootStateData {
|
|
||||||
uint32 copper = 0;
|
|
||||||
uint32 silver = 0;
|
|
||||||
uint32 gold = 0;
|
|
||||||
uint32 platinum = 0;
|
|
||||||
std::vector<LootEntryStateData> entries = {};
|
|
||||||
|
|
||||||
// cereal
|
|
||||||
template<class Archive>
|
|
||||||
void serialize(Archive &ar)
|
|
||||||
{
|
|
||||||
ar(
|
|
||||||
CEREAL_NVP(copper),
|
|
||||||
CEREAL_NVP(silver),
|
|
||||||
CEREAL_NVP(gold),
|
|
||||||
CEREAL_NVP(platinum),
|
|
||||||
CEREAL_NVP(entries)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// IsZoneStateValid checks if the zone state is valid
|
// IsZoneStateValid checks if the zone state is valid
|
||||||
// if these fields are all empty or zero value for an entire zone state, it's considered invalid
|
// if these fields are all empty or zero value for an entire zone state, it's considered invalid
|
||||||
@ -359,7 +320,7 @@ inline void LoadNPCState(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStat
|
|||||||
auto decay_time = s.decay_in_seconds * 1000;
|
auto decay_time = s.decay_in_seconds * 1000;
|
||||||
if (decay_time > 0) {
|
if (decay_time > 0) {
|
||||||
n->SetQueuedToCorpse();
|
n->SetQueuedToCorpse();
|
||||||
n->SetCorpseDecayTime(decay_time);
|
entity_list.RestoreCorpse(n, decay_time);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
n->Depop();
|
n->Depop();
|
||||||
@ -454,6 +415,7 @@ bool Zone::LoadZoneState(
|
|||||||
zone->Process();
|
zone->Process();
|
||||||
|
|
||||||
// load zone variables first
|
// load zone variables first
|
||||||
|
int count = 0;
|
||||||
for (auto &s: spawn_states) {
|
for (auto &s: spawn_states) {
|
||||||
if (s.is_zone) {
|
if (s.is_zone) {
|
||||||
LoadZoneVariables(zone, s.entity_variables);
|
LoadZoneVariables(zone, s.entity_variables);
|
||||||
@ -502,11 +464,13 @@ bool Zone::LoadZoneState(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (spawn_time_left == 0) {
|
if (spawn_time_left == 0) {
|
||||||
new_spawn->SetCurrentNPCID(s.npc_id);
|
new_spawn->SetResumedNPCID(s.npc_id);
|
||||||
new_spawn->SetResumedFromZoneSuspend(true);
|
new_spawn->SetResumedFromZoneSuspend(true);
|
||||||
new_spawn->SetEntityVariables(GetVariablesDeserialized(s.entity_variables));
|
new_spawn->SetEntityVariables(GetVariablesDeserialized(s.entity_variables));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
|
||||||
spawn2_list.Insert(new_spawn);
|
spawn2_list.Insert(new_spawn);
|
||||||
new_spawn->Process();
|
new_spawn->Process();
|
||||||
auto n = new_spawn->GetNPC();
|
auto n = new_spawn->GetNPC();
|
||||||
@ -548,24 +512,6 @@ bool Zone::LoadZoneState(
|
|||||||
LoadNPCState(zone, npc, s);
|
LoadNPCState(zone, npc, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
// any NPC that is spawned by the spawn system
|
|
||||||
for (auto &e: entity_list.GetNPCList()) {
|
|
||||||
auto npc = e.second;
|
|
||||||
if (npc->GetSpawnGroupId() == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto &s: spawn_states) {
|
|
||||||
bool is_same_npc =
|
|
||||||
s.npc_id == npc->GetNPCTypeID() &&
|
|
||||||
s.spawn2_id == npc->GetSpawnPointID() &&
|
|
||||||
s.spawngroup_id == npc->GetSpawnGroupId();
|
|
||||||
if (is_same_npc) {
|
|
||||||
LoadNPCState(zone, npc, s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !spawn_states.empty();
|
return !spawn_states.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -638,6 +584,7 @@ void Zone::SaveZoneState()
|
|||||||
std::vector<ZoneStateSpawnsRepository::ZoneStateSpawns> spawns = {};
|
std::vector<ZoneStateSpawnsRepository::ZoneStateSpawns> spawns = {};
|
||||||
LinkedListIterator<Spawn2 *> iterator(spawn2_list);
|
LinkedListIterator<Spawn2 *> iterator(spawn2_list);
|
||||||
iterator.Reset();
|
iterator.Reset();
|
||||||
|
int count = 0;
|
||||||
while (iterator.MoreElements()) {
|
while (iterator.MoreElements()) {
|
||||||
Spawn2 *sp = iterator.GetData();
|
Spawn2 *sp = iterator.GetData();
|
||||||
auto s = ZoneStateSpawnsRepository::NewEntity();
|
auto s = ZoneStateSpawnsRepository::NewEntity();
|
||||||
@ -667,6 +614,7 @@ void Zone::SaveZoneState()
|
|||||||
|
|
||||||
spawns.emplace_back(s);
|
spawns.emplace_back(s);
|
||||||
iterator.Advance();
|
iterator.Advance();
|
||||||
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// npc's that are not in the spawn2 list
|
// npc's that are not in the spawn2 list
|
||||||
@ -742,6 +690,11 @@ void Zone::SaveZoneState()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (spawns.empty()) {
|
||||||
|
LogInfo("No zone state data to save");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ZoneStateSpawnsRepository::InsertMany(database, spawns);
|
ZoneStateSpawnsRepository::InsertMany(database, spawns);
|
||||||
|
|
||||||
LogInfo("Saved [{}] zone state spawns", Strings::Commify(spawns.size()));
|
LogInfo("Saved [{}] zone state spawns", Strings::Commify(spawns.size()));
|
||||||
|
|||||||
48
zone/zone_save_state.h
Normal file
48
zone/zone_save_state.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <cereal/archives/json.hpp>
|
||||||
|
#include <cereal/types/map.hpp>
|
||||||
|
#include "npc.h"
|
||||||
|
#include "corpse.h"
|
||||||
|
#include "zone.h"
|
||||||
|
#include "../common/repositories/zone_state_spawns_repository.h"
|
||||||
|
#include "../common/repositories/spawn2_disabled_repository.h"
|
||||||
|
|
||||||
|
struct LootEntryStateData {
|
||||||
|
uint32 item_id = 0;
|
||||||
|
uint32_t lootdrop_id = 0;
|
||||||
|
uint16 charges = 0; // used in dynamically added loot (AddItem)
|
||||||
|
|
||||||
|
// cereal
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive &ar)
|
||||||
|
{
|
||||||
|
ar(
|
||||||
|
CEREAL_NVP(item_id),
|
||||||
|
CEREAL_NVP(lootdrop_id),
|
||||||
|
CEREAL_NVP(charges)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LootStateData {
|
||||||
|
uint32 copper = 0;
|
||||||
|
uint32 silver = 0;
|
||||||
|
uint32 gold = 0;
|
||||||
|
uint32 platinum = 0;
|
||||||
|
std::vector<LootEntryStateData> entries = {};
|
||||||
|
|
||||||
|
// cereal
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive &ar)
|
||||||
|
{
|
||||||
|
ar(
|
||||||
|
CEREAL_NVP(copper),
|
||||||
|
CEREAL_NVP(silver),
|
||||||
|
CEREAL_NVP(gold),
|
||||||
|
CEREAL_NVP(platinum),
|
||||||
|
CEREAL_NVP(entries)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user