mirror of
https://github.com/EQEmu/Server.git
synced 2026-06-13 02:38:45 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c2545cfaf | |||
| 8d1a9efac9 | |||
| f6b18fb003 | |||
| 00e77f190c | |||
| 9cb72a6ba7 |
@@ -1,3 +1,21 @@
|
|||||||
|
## [23.3.3] 3/13/2025
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
* Add indexes for data_buckets and zone_state_spawns ([#4771](https://github.com/EQEmu/Server/pull/4771)) @Akkadius 2025-03-11
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* Update GuildBank to correctly handle items with charges equal to zero ([#4774](https://github.com/EQEmu/Server/pull/4774)) @neckkola 2025-03-13
|
||||||
|
|
||||||
|
### Networking
|
||||||
|
|
||||||
|
* Fix "port in use" error ([#4772](https://github.com/EQEmu/Server/pull/4772)) @Akkadius 2025-03-12
|
||||||
|
|
||||||
|
### Zone
|
||||||
|
|
||||||
|
* Zone State Improvements Part 3 ([#4773](https://github.com/EQEmu/Server/pull/4773)) @Akkadius 2025-03-13
|
||||||
|
|
||||||
## [23.3.2] 3/11/2025
|
## [23.3.2] 3/11/2025
|
||||||
|
|
||||||
### DynamicZones
|
### DynamicZones
|
||||||
|
|||||||
@@ -6980,7 +6980,7 @@ ALTER TABLE data_buckets ADD INDEX idx_bot_expires (bot_id, expires);
|
|||||||
},
|
},
|
||||||
ManifestEntry{
|
ManifestEntry{
|
||||||
.version = 9313,
|
.version = 9313,
|
||||||
.description = "2025_03_11_data_bucket_indexes.sql",
|
.description = "2025_03_11_zone_state_spawns.sql",
|
||||||
.check = "SHOW INDEX FROM zone_state_spawns",
|
.check = "SHOW INDEX FROM zone_state_spawns",
|
||||||
.condition = "missing",
|
.condition = "missing",
|
||||||
.match = "idx_zone_instance",
|
.match = "idx_zone_instance",
|
||||||
@@ -6990,6 +6990,17 @@ ALTER TABLE zone_state_spawns ADD INDEX idx_instance_id (instance_id);
|
|||||||
)",
|
)",
|
||||||
.content_schema_update = false
|
.content_schema_update = false
|
||||||
},
|
},
|
||||||
|
ManifestEntry{
|
||||||
|
.version = 9314,
|
||||||
|
.description = "2025_03_12_zone_state_spawns_one_time_truncate.sql",
|
||||||
|
.check = "SELECT * FROM db_version WHERE version >= 9314",
|
||||||
|
.condition = "empty",
|
||||||
|
.match = "",
|
||||||
|
.sql = R"(
|
||||||
|
TRUNCATE TABLE zone_state_spawns;
|
||||||
|
)",
|
||||||
|
.content_schema_update = false
|
||||||
|
},
|
||||||
// -- template; copy/paste this when you need to create a new entry
|
// -- template; copy/paste this when you need to create a new entry
|
||||||
// ManifestEntry{
|
// ManifestEntry{
|
||||||
// .version = 9228,
|
// .version = 9228,
|
||||||
|
|||||||
@@ -303,6 +303,14 @@ bool IpUtil::IsPortInUse(const std::string& ip, int port) {
|
|||||||
return true; // Assume in use on failure
|
return true; // Assume in use on failure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
int opt = 1;
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&opt, sizeof(opt)); // Windows-specific
|
||||||
|
#else
|
||||||
|
int opt = 1;
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // Linux/macOS
|
||||||
|
#endif
|
||||||
|
|
||||||
sockaddr_in addr{};
|
sockaddr_in addr{};
|
||||||
addr.sin_family = AF_INET;
|
addr.sin_family = AF_INET;
|
||||||
addr.sin_port = htons(port);
|
addr.sin_port = htons(port);
|
||||||
|
|||||||
@@ -5,9 +5,77 @@
|
|||||||
#include "../strings.h"
|
#include "../strings.h"
|
||||||
#include "base/base_zone_state_spawns_repository.h"
|
#include "base/base_zone_state_spawns_repository.h"
|
||||||
|
|
||||||
class ZoneStateSpawnsRepository: public BaseZoneStateSpawnsRepository {
|
class ZoneStateSpawnsRepository : public BaseZoneStateSpawnsRepository {
|
||||||
public:
|
public:
|
||||||
// Custom extended repository methods here
|
static void PurgeInvalidZoneStates(Database &database)
|
||||||
|
{
|
||||||
|
std::string query = R"(
|
||||||
|
SELECT zone_id, instance_id
|
||||||
|
FROM zone_state_spawns
|
||||||
|
GROUP BY zone_id, instance_id
|
||||||
|
HAVING COUNT(*) = SUM(
|
||||||
|
CASE
|
||||||
|
WHEN hp = 0
|
||||||
|
AND mana = 0
|
||||||
|
AND endurance = 0
|
||||||
|
AND (loot_data IS NULL OR loot_data = '')
|
||||||
|
AND (entity_variables IS NULL OR entity_variables = '')
|
||||||
|
AND (buffs IS NULL OR buffs = '')
|
||||||
|
THEN 1 ELSE 0
|
||||||
|
END
|
||||||
|
);
|
||||||
|
)";
|
||||||
|
|
||||||
|
auto results = database.QueryDatabase(query);
|
||||||
|
if (!results.Success()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto row: results) {
|
||||||
|
uint32 zone_id = std::stoul(row[0]);
|
||||||
|
uint32 instance_id = std::stoul(row[1]);
|
||||||
|
|
||||||
|
int rows = ZoneStateSpawnsRepository::DeleteWhere(
|
||||||
|
database,
|
||||||
|
fmt::format(
|
||||||
|
"`zone_id` = {} AND `instance_id` = {}",
|
||||||
|
zone_id,
|
||||||
|
instance_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
LogInfo(
|
||||||
|
"Purged invalid zone state data for zone [{}] instance [{}] rows [{}]",
|
||||||
|
zone_id,
|
||||||
|
instance_id,
|
||||||
|
Strings::Commify(rows)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PurgeOldZoneStates(Database &database)
|
||||||
|
{
|
||||||
|
int days = RuleI(Zone, StateSaveClearDays);
|
||||||
|
|
||||||
|
std::string query = fmt::format(
|
||||||
|
"DELETE FROM zone_state_spawns WHERE created_at < NOW() - INTERVAL {} DAY",
|
||||||
|
days
|
||||||
|
);
|
||||||
|
|
||||||
|
auto results = database.QueryDatabase(query);
|
||||||
|
if (!results.Success()) {
|
||||||
|
LogError("Failed to purge old zone state data older than {} days.", days);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.RowsAffected() > 0) {
|
||||||
|
LogInfo(
|
||||||
|
"Purged old zone state data older than days [{}] rows [{}]",
|
||||||
|
days,
|
||||||
|
Strings::Commify(results.RowsAffected())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -376,6 +376,7 @@ RULE_BOOL(Zone, AllowCrossZoneSpellsOnPets, false, "Set to true to allow cross z
|
|||||||
RULE_BOOL(Zone, ZoneShardQuestMenuOnly, false, "Set to true if you only want quests to show the zone shard menu")
|
RULE_BOOL(Zone, ZoneShardQuestMenuOnly, false, "Set to true if you only want quests to show the zone shard menu")
|
||||||
RULE_BOOL(Zone, StateSaveEntityVariables, true, "Set to true if you want buffs to be saved on shutdown")
|
RULE_BOOL(Zone, StateSaveEntityVariables, true, "Set to true if you want buffs to be saved on shutdown")
|
||||||
RULE_BOOL(Zone, StateSaveBuffs, true, "Set to true if you want buffs to be saved on shutdown")
|
RULE_BOOL(Zone, StateSaveBuffs, true, "Set to true if you want buffs to be saved on shutdown")
|
||||||
|
RULE_INT(Zone, StateSaveClearDays, 7, "Clears state save data older than this many days")
|
||||||
RULE_BOOL(Zone, StateSavingOnShutdown, true, "Set to true if you want zones to save state on shutdown (npcs, corpses, loot, entity variables, buffs etc.)")
|
RULE_BOOL(Zone, StateSavingOnShutdown, true, "Set to true if you want zones to save state on shutdown (npcs, corpses, loot, entity variables, buffs etc.)")
|
||||||
RULE_CATEGORY_END()
|
RULE_CATEGORY_END()
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
// Build variables
|
// Build variables
|
||||||
// these get injected during the build pipeline
|
// these get injected during the build pipeline
|
||||||
#define CURRENT_VERSION "23.3.2-dev" // always append -dev to the current version for custom-builds
|
#define CURRENT_VERSION "23.3.3-dev" // always append -dev to the current version for custom-builds
|
||||||
#define LOGIN_VERSION "0.8.0"
|
#define LOGIN_VERSION "0.8.0"
|
||||||
#define COMPILE_DATE __DATE__
|
#define COMPILE_DATE __DATE__
|
||||||
#define COMPILE_TIME __TIME__
|
#define COMPILE_TIME __TIME__
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define CURRENT_BINARY_DATABASE_VERSION 9313
|
#define CURRENT_BINARY_DATABASE_VERSION 9314
|
||||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
|
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "eqemu-server",
|
"name": "eqemu-server",
|
||||||
"version": "23.3.2",
|
"version": "23.3.3",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/EQEmu/Server.git"
|
"url": "https://github.com/EQEmu/Server.git"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/golang/protobuf v1.3.2 // indirect
|
github.com/golang/protobuf v1.3.2 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
golang.org/x/crypto v0.31.0 // indirect
|
golang.org/x/crypto v0.35.0 // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/net v0.36.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
|
|||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "../common/zone_store.h"
|
#include "../common/zone_store.h"
|
||||||
#include "../common/path_manager.h"
|
#include "../common/path_manager.h"
|
||||||
#include "../common/database/database_update.h"
|
#include "../common/database/database_update.h"
|
||||||
|
#include "../common/repositories/zone_state_spawns_repository.h"
|
||||||
|
|
||||||
extern ZSList zoneserver_list;
|
extern ZSList zoneserver_list;
|
||||||
extern WorldConfig Config;
|
extern WorldConfig Config;
|
||||||
@@ -412,6 +413,11 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv)
|
|||||||
LogInfo("Cleaning up instance corpses");
|
LogInfo("Cleaning up instance corpses");
|
||||||
database.CleanupInstanceCorpses();
|
database.CleanupInstanceCorpses();
|
||||||
|
|
||||||
|
if (RuleB(Zone, StateSavingOnShutdown)) {
|
||||||
|
ZoneStateSpawnsRepository::PurgeInvalidZoneStates(database);
|
||||||
|
ZoneStateSpawnsRepository::PurgeOldZoneStates(database);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-7
@@ -7653,7 +7653,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
|
|||||||
log.char_id = CharacterID();
|
log.char_id = CharacterID();
|
||||||
log.guild_id = GuildID();
|
log.guild_id = GuildID();
|
||||||
log.item_id = inst->GetID();
|
log.item_id = inst->GetID();
|
||||||
log.quantity = inst->GetCharges();
|
log.quantity = 1;
|
||||||
|
if (inst->GetCharges() > 0 || inst->IsStackable() || inst->GetItem()->MaxCharges > 0) {
|
||||||
|
log.quantity = inst->GetCharges();
|
||||||
|
}
|
||||||
|
|
||||||
if (inst->IsAugmented()) {
|
if (inst->IsAugmented()) {
|
||||||
auto augs = inst->GetAugmentIDs();
|
auto augs = inst->GetAugmentIDs();
|
||||||
log.aug_slot_one = augs.at(0);
|
log.aug_slot_one = augs.at(0);
|
||||||
@@ -7737,7 +7741,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
|
|||||||
item.guild_id = GuildID();
|
item.guild_id = GuildID();
|
||||||
item.area = GuildBankDepositArea;
|
item.area = GuildBankDepositArea;
|
||||||
item.item_id = cursor_item->ID;
|
item.item_id = cursor_item->ID;
|
||||||
item.quantity = cursor_item_inst->GetCharges();
|
item.quantity = 1;
|
||||||
|
if (cursor_item_inst->GetCharges() > 0 || cursor_item_inst->IsStackable() || cursor_item->MaxCharges > 0) {
|
||||||
|
item.quantity = cursor_item_inst->GetCharges();
|
||||||
|
}
|
||||||
|
|
||||||
item.donator = GetCleanName();
|
item.donator = GetCleanName();
|
||||||
item.permissions = GuildBankBankerOnly;
|
item.permissions = GuildBankBankerOnly;
|
||||||
if (cursor_item_inst->IsAugmented()) {
|
if (cursor_item_inst->IsAugmented()) {
|
||||||
@@ -7821,14 +7829,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inst->GetCharges() > 0) {
|
gbwis->Quantity = 1;
|
||||||
|
if (inst->GetCharges() > 0 || inst->IsStackable() || inst->GetItem()->MaxCharges > 0) {
|
||||||
gbwis->Quantity = inst->GetCharges();
|
gbwis->Quantity = inst->GetCharges();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inst->GetCharges() < 0) {
|
|
||||||
gbwis->Quantity = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
PushItemOnCursor(*inst.get());
|
PushItemOnCursor(*inst.get());
|
||||||
SendItemPacket(EQ::invslot::slotCursor, inst.get(), ItemPacketLimbo);
|
SendItemPacket(EQ::invslot::slotCursor, inst.get(), ItemPacketLimbo);
|
||||||
GuildBanks->DeleteItem(GuildID(), gbwis->Area, gbwis->SlotID, gbwis->Quantity, this);
|
GuildBanks->DeleteItem(GuildID(), gbwis->Area, gbwis->SlotID, gbwis->Quantity, this);
|
||||||
|
|||||||
+6
-1
@@ -23,6 +23,11 @@ void NPC::AddLootTable(uint32 loottable_id, bool is_global)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_resumed_from_zone_suspend) {
|
||||||
|
LogZoneState("NPC [{}] is resuming from zone suspend, skipping", GetCleanName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!is_global) {
|
if (!is_global) {
|
||||||
m_loot_copper = 0;
|
m_loot_copper = 0;
|
||||||
m_loot_silver = 0;
|
m_loot_silver = 0;
|
||||||
@@ -277,7 +282,7 @@ void NPC::AddLootDrop(
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (m_resumed_from_zone_suspend) {
|
if (m_resumed_from_zone_suspend) {
|
||||||
LogZoneState("NPC [{}] is resuming from zone suspend, skipping AddItem", GetCleanName());
|
LogZoneState("NPC [{}] is resuming from zone suspend, skipping", GetCleanName());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -626,6 +626,9 @@ inline void NPCCommandsMenu(Client* client, NPC* npc)
|
|||||||
|
|
||||||
if (npc->GetLoottableID() > 0) {
|
if (npc->GetLoottableID() > 0) {
|
||||||
menu_commands += "[" + Saylink::Silent("#npcloot show", "Loot") + "] ";
|
menu_commands += "[" + Saylink::Silent("#npcloot show", "Loot") + "] ";
|
||||||
|
if (npc) {
|
||||||
|
menu_commands += fmt::format(" Item(s) ({}) ", npc->GetLootItems().size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (npc->IsProximitySet()) {
|
if (npc->IsProximitySet()) {
|
||||||
|
|||||||
+1
-1
@@ -134,7 +134,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
|||||||
swarm_timer(100),
|
swarm_timer(100),
|
||||||
m_corpse_queue_timer(1000),
|
m_corpse_queue_timer(1000),
|
||||||
m_corpse_queue_shutoff_timer(30000),
|
m_corpse_queue_shutoff_timer(30000),
|
||||||
m_resumed_from_zone_suspend_shutoff_timer(30000),
|
m_resumed_from_zone_suspend_shutoff_timer(10000),
|
||||||
classattack_timer(1000),
|
classattack_timer(1000),
|
||||||
monkattack_timer(1000),
|
monkattack_timer(1000),
|
||||||
knightattack_timer(1000),
|
knightattack_timer(1000),
|
||||||
|
|||||||
@@ -435,10 +435,8 @@ int QuestParserCollection::EventNPC(
|
|||||||
std::vector<std::any>* extra_pointers
|
std::vector<std::any>* extra_pointers
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (npc->IsResumedFromZoneSuspend()) {
|
if (npc->IsResumedFromZoneSuspend() && npc->IsQueuedForCorpse()) {
|
||||||
if (event_id == EVENT_DEATH_COMPLETE || event_id == EVENT_DEATH) {
|
return 0;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const int local_return = EventNPCLocal(event_id, npc, init, data, extra_data, extra_pointers);
|
const int local_return = EventNPCLocal(event_id, npc, init, data, extra_data, extra_pointers);
|
||||||
|
|||||||
@@ -208,6 +208,15 @@ void QuestManager::write(const char *file, const char *str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Mob* QuestManager::spawn2(int npc_id, int grid, int unused, const glm::vec4& position) {
|
Mob* QuestManager::spawn2(int npc_id, int grid, int unused, const glm::vec4& position) {
|
||||||
|
QuestManagerCurrentQuestVars();
|
||||||
|
if (owner && owner->IsNPC()) {
|
||||||
|
auto n = owner->CastToNPC();
|
||||||
|
if (n->IsResumedFromZoneSuspend()) {
|
||||||
|
LogZoneState("NPC [{}] is resuming from zone suspend, skipping quest call", n->GetCleanName());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const NPCType* t = 0;
|
const NPCType* t = 0;
|
||||||
if (t = content_db.LoadNPCTypesData(npc_id)) {
|
if (t = content_db.LoadNPCTypesData(npc_id)) {
|
||||||
auto npc = new NPC(t, nullptr, position, GravityBehavior::Water);
|
auto npc = new NPC(t, nullptr, position, GravityBehavior::Water);
|
||||||
@@ -228,6 +237,15 @@ Mob* QuestManager::spawn2(int npc_id, int grid, int unused, const glm::vec4& pos
|
|||||||
}
|
}
|
||||||
|
|
||||||
Mob* QuestManager::unique_spawn(int npc_type, int grid, int unused, const glm::vec4& position) {
|
Mob* QuestManager::unique_spawn(int npc_type, int grid, int unused, const glm::vec4& position) {
|
||||||
|
QuestManagerCurrentQuestVars();
|
||||||
|
if (owner && owner->IsNPC()) {
|
||||||
|
auto n = owner->CastToNPC();
|
||||||
|
if (n->IsResumedFromZoneSuspend()) {
|
||||||
|
LogZoneState("NPC [{}] is resuming from zone suspend, skipping quest call", n->GetCleanName());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Mob *other = entity_list.GetMobByNpcTypeID(npc_type);
|
Mob *other = entity_list.GetMobByNpcTypeID(npc_type);
|
||||||
if(other != nullptr) {
|
if(other != nullptr) {
|
||||||
return other;
|
return other;
|
||||||
|
|||||||
@@ -277,6 +277,9 @@ bool Spawn2::Process() {
|
|||||||
|
|
||||||
npcthis = npc;
|
npcthis = npc;
|
||||||
|
|
||||||
|
npc->SetResumedFromZoneSuspend(m_resumed_from_zone_suspend);
|
||||||
|
m_resumed_from_zone_suspend = false;
|
||||||
|
|
||||||
npc->AddLootTable();
|
npc->AddLootTable();
|
||||||
if (npc->DropsGlobalLoot()) {
|
if (npc->DropsGlobalLoot()) {
|
||||||
npc->CheckGlobalLootTables();
|
npc->CheckGlobalLootTables();
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ public:
|
|||||||
int16 GetConditionMinValue() const { return condition_min_value; }
|
int16 GetConditionMinValue() const { return condition_min_value; }
|
||||||
int16 GetAnimation () { return anim; }
|
int16 GetAnimation () { return anim; }
|
||||||
inline NPC *GetNPC() const { return npcthis; }
|
inline NPC *GetNPC() const { return npcthis; }
|
||||||
|
inline bool IsResumedFromZoneSuspend() const { return m_resumed_from_zone_suspend; }
|
||||||
|
inline void SetResumedFromZoneSuspend(bool resumed) { m_resumed_from_zone_suspend = resumed; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class Zone;
|
friend class Zone;
|
||||||
@@ -101,6 +103,7 @@ private:
|
|||||||
EmuAppearance anim;
|
EmuAppearance anim;
|
||||||
bool IsDespawned;
|
bool IsDespawned;
|
||||||
uint32 killcount;
|
uint32 killcount;
|
||||||
|
bool m_resumed_from_zone_suspend = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SpawnCondition {
|
class SpawnCondition {
|
||||||
|
|||||||
+4
-1
@@ -887,7 +887,10 @@ void Zone::Shutdown(bool quiet)
|
|||||||
c.second->WorldKick();
|
c.second->WorldKick();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RuleB(Zone, StateSavingOnShutdown)) {
|
bool does_zone_have_entities =
|
||||||
|
zone && zone->IsLoaded() &&
|
||||||
|
(!entity_list.GetNPCList().empty() || !entity_list.GetCorpseList().empty());
|
||||||
|
if (RuleB(Zone, StateSavingOnShutdown) && does_zone_have_entities) {
|
||||||
SaveZoneState();
|
SaveZoneState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+105
-37
@@ -45,10 +45,40 @@ struct LootStateData {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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
|
||||||
|
inline bool IsZoneStateValid(std::vector<ZoneStateSpawnsRepository::ZoneStateSpawns> &spawns)
|
||||||
|
{
|
||||||
|
return std::any_of(
|
||||||
|
spawns.begin(), spawns.end(), [](const auto &s) {
|
||||||
|
return !(
|
||||||
|
s.hp == 0 &&
|
||||||
|
s.mana == 0 &&
|
||||||
|
s.endurance == 0 &&
|
||||||
|
s.loot_data.empty() &&
|
||||||
|
s.entity_variables.empty() &&
|
||||||
|
s.buffs.empty()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
inline void LoadLootStateData(Zone *zone, NPC *npc, const std::string &loot_data)
|
inline void LoadLootStateData(Zone *zone, NPC *npc, const std::string &loot_data)
|
||||||
{
|
{
|
||||||
LootStateData l{};
|
LootStateData l{};
|
||||||
|
|
||||||
|
// in the event that should never happen, we roll loot from the NPC's table
|
||||||
|
if (loot_data.empty()) {
|
||||||
|
LogZoneState("No loot state data found for NPC [{}], re-rolling", npc->GetNPCTypeID());
|
||||||
|
npc->ClearLootItems();
|
||||||
|
npc->AddLootTable();
|
||||||
|
if (npc->DropsGlobalLoot()) {
|
||||||
|
npc->CheckGlobalLootTables();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Strings::IsValidJson(loot_data)) {
|
if (!Strings::IsValidJson(loot_data)) {
|
||||||
LogZoneState("Invalid JSON data for NPC [{}]", npc->GetNPCTypeID());
|
LogZoneState("Invalid JSON data for NPC [{}]", npc->GetNPCTypeID());
|
||||||
return;
|
return;
|
||||||
@@ -66,6 +96,11 @@ inline void LoadLootStateData(Zone *zone, NPC *npc, const std::string &loot_data
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset
|
||||||
|
npc->RemoveLootCash();
|
||||||
|
npc->ClearLootItems();
|
||||||
|
|
||||||
|
// add loot
|
||||||
npc->AddLootCash(l.copper, l.silver, l.gold, l.platinum);
|
npc->AddLootCash(l.copper, l.silver, l.gold, l.platinum);
|
||||||
|
|
||||||
for (auto &e: l.entries) {
|
for (auto &e: l.entries) {
|
||||||
@@ -76,7 +111,7 @@ inline void LoadLootStateData(Zone *zone, NPC *npc, const std::string &loot_data
|
|||||||
|
|
||||||
// dynamically added via AddItem
|
// dynamically added via AddItem
|
||||||
if (e.lootdrop_id == 0) {
|
if (e.lootdrop_id == 0) {
|
||||||
npc->AddItem(e.item_id, e.charges);
|
npc->AddItem(e.item_id, e.charges, true);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +210,10 @@ inline void LoadNPCEntityVariables(NPC *n, const std::string &entity_variables)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entity_variables.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Strings::IsValidJson(entity_variables)) {
|
if (!Strings::IsValidJson(entity_variables)) {
|
||||||
LogZoneState("Invalid JSON data for NPC [{}]", n->GetNPCTypeID());
|
LogZoneState("Invalid JSON data for NPC [{}]", n->GetNPCTypeID());
|
||||||
return;
|
return;
|
||||||
@@ -204,6 +243,10 @@ inline void LoadNPCBuffs(NPC *n, const std::string &buffs)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buffs.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Strings::IsValidJson(buffs)) {
|
if (!Strings::IsValidJson(buffs)) {
|
||||||
LogZoneState("Invalid JSON data for NPC [{}]", n->GetNPCTypeID());
|
LogZoneState("Invalid JSON data for NPC [{}]", n->GetNPCTypeID());
|
||||||
return;
|
return;
|
||||||
@@ -236,6 +279,10 @@ inline std::vector<uint32_t> GetLootdropIds(const std::vector<ZoneStateSpawnsRep
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Strings::IsValidJson(s.loot_data)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
LootStateData l{};
|
LootStateData l{};
|
||||||
try {
|
try {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
@@ -273,7 +320,9 @@ inline void LoadNPCState(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStat
|
|||||||
n->AssignWaypoints(s.grid, s.current_waypoint);
|
n->AssignWaypoints(s.grid, s.current_waypoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n->SetResumedFromZoneSuspend(false);
|
||||||
LoadLootStateData(zone, n, s.loot_data);
|
LoadLootStateData(zone, n, s.loot_data);
|
||||||
|
n->SetResumedFromZoneSuspend(true);
|
||||||
LoadNPCEntityVariables(n, s.entity_variables);
|
LoadNPCEntityVariables(n, s.entity_variables);
|
||||||
LoadNPCBuffs(n, s.buffs);
|
LoadNPCBuffs(n, s.buffs);
|
||||||
|
|
||||||
@@ -348,7 +397,7 @@ bool Zone::LoadZoneState(
|
|||||||
auto spawn_states = ZoneStateSpawnsRepository::GetWhere(
|
auto spawn_states = ZoneStateSpawnsRepository::GetWhere(
|
||||||
database,
|
database,
|
||||||
fmt::format(
|
fmt::format(
|
||||||
"zone_id = {} AND instance_id = {}",
|
"zone_id = {} AND instance_id = {} ORDER BY spawn2_id",
|
||||||
zoneid,
|
zoneid,
|
||||||
zone->GetInstanceID()
|
zone->GetInstanceID()
|
||||||
)
|
)
|
||||||
@@ -356,6 +405,16 @@ bool Zone::LoadZoneState(
|
|||||||
|
|
||||||
LogInfo("Loading zone state spawns for zone [{}] spawns [{}]", GetShortName(), spawn_states.size());
|
LogInfo("Loading zone state spawns for zone [{}] spawns [{}]", GetShortName(), spawn_states.size());
|
||||||
|
|
||||||
|
if (spawn_states.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsZoneStateValid(spawn_states)) {
|
||||||
|
LogZoneState("Invalid zone state data for zone [{}]", GetShortName());
|
||||||
|
ClearZoneState(zoneid, zone->GetInstanceID());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint32_t> lootdrop_ids = GetLootdropIds(spawn_states);
|
std::vector<uint32_t> lootdrop_ids = GetLootdropIds(spawn_states);
|
||||||
zone->LoadLootDrops(lootdrop_ids);
|
zone->LoadLootDrops(lootdrop_ids);
|
||||||
|
|
||||||
@@ -409,16 +468,14 @@ bool Zone::LoadZoneState(
|
|||||||
|
|
||||||
if (spawn_time_left == 0) {
|
if (spawn_time_left == 0) {
|
||||||
new_spawn->SetCurrentNPCID(s.npc_id);
|
new_spawn->SetCurrentNPCID(s.npc_id);
|
||||||
|
new_spawn->SetResumedFromZoneSuspend(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
if (n) {
|
if (n) {
|
||||||
n->ClearLootItems();
|
LoadNPCState(zone, n, s);
|
||||||
if (s.grid > 0) {
|
|
||||||
n->AssignWaypoints(s.grid, s.current_waypoint);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,6 +498,13 @@ bool Zone::LoadZoneState(
|
|||||||
GravityBehavior::Water
|
GravityBehavior::Water
|
||||||
);
|
);
|
||||||
|
|
||||||
|
npc->SetResumedFromZoneSuspend(true);
|
||||||
|
|
||||||
|
// tag as corpse before we add to entity list to prevent quest triggers
|
||||||
|
if (s.is_corpse) {
|
||||||
|
npc->SetQueuedToCorpse();
|
||||||
|
}
|
||||||
|
|
||||||
entity_list.AddNPC(npc, true, true);
|
entity_list.AddNPC(npc, true, true);
|
||||||
|
|
||||||
LoadNPCState(zone, npc, s);
|
LoadNPCState(zone, npc, s);
|
||||||
@@ -476,44 +540,43 @@ inline void SaveNPCState(NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s)
|
|||||||
variables[k] = n->GetEntityVariable(k);
|
variables[k] = n->GetEntityVariable(k);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (!variables.empty()) {
|
||||||
std::ostringstream os;
|
try {
|
||||||
{
|
std::ostringstream os;
|
||||||
cereal::JSONOutputArchiveSingleLine archive(os);
|
{
|
||||||
archive(variables);
|
cereal::JSONOutputArchiveSingleLine archive(os);
|
||||||
|
archive(variables);
|
||||||
|
}
|
||||||
|
s.entity_variables = os.str();
|
||||||
|
}
|
||||||
|
catch (const std::exception &e) {
|
||||||
|
LogZoneState("Failed to serialize entity variables for NPC [{}] [{}]", n->GetNPCTypeID(), e.what());
|
||||||
}
|
}
|
||||||
s.entity_variables = os.str();
|
|
||||||
}
|
|
||||||
catch (const std::exception &e) {
|
|
||||||
LogZoneState("Failed to serialize entity variables for NPC [{}] [{}]", n->GetNPCTypeID(), e.what());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// buffs
|
// buffs
|
||||||
auto buffs = n->GetBuffs();
|
auto buffs = n->GetBuffs();
|
||||||
if (!buffs) {
|
if (buffs) {
|
||||||
return;
|
std::vector<Buffs_Struct> valid_buffs;
|
||||||
}
|
for (int index = 0; index < n->GetMaxBuffSlots(); index++) {
|
||||||
|
if (buffs[index].spellid != 0 && buffs[index].spellid != 65535) {
|
||||||
std::vector<Buffs_Struct> valid_buffs;
|
valid_buffs.push_back(buffs[index]);
|
||||||
|
}
|
||||||
for (int index = 0; index < n->GetMaxBuffSlots(); index++) {
|
|
||||||
if (buffs[index].spellid != 0 && buffs[index].spellid != 65535) {
|
|
||||||
valid_buffs.push_back(buffs[index]);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
if (!valid_buffs.empty()) {
|
||||||
std::ostringstream os = std::ostringstream();
|
try {
|
||||||
{
|
std::ostringstream os = std::ostringstream();
|
||||||
cereal::JSONOutputArchiveSingleLine archive(os);
|
{
|
||||||
archive(cereal::make_nvp("buffs", valid_buffs));
|
cereal::JSONOutputArchiveSingleLine archive(os);
|
||||||
|
archive(cereal::make_nvp("buffs", valid_buffs));
|
||||||
|
}
|
||||||
|
s.buffs = os.str();
|
||||||
|
}
|
||||||
|
catch (const std::exception &e) {
|
||||||
|
LogZoneState("Failed to serialize buffs for NPC [{}] [{}]", n->GetNPCTypeID(), e.what());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s.buffs = os.str();
|
|
||||||
}
|
|
||||||
catch (const std::exception &e) {
|
|
||||||
LogZoneState("Failed to serialize buffs for NPC [{}] [{}]", n->GetNPCTypeID(), e.what());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// rest
|
// rest
|
||||||
@@ -568,7 +631,7 @@ void Zone::SaveZoneState()
|
|||||||
iterator.Advance();
|
iterator.Advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
// npcs that are not in the spawn2 list
|
// npc's that are not in the spawn2 list
|
||||||
for (auto &n: entity_list.GetNPCList()) {
|
for (auto &n: entity_list.GetNPCList()) {
|
||||||
// everything below here is dynamically spawned
|
// everything below here is dynamically spawned
|
||||||
bool ignore_npcs =
|
bool ignore_npcs =
|
||||||
@@ -636,6 +699,11 @@ void Zone::SaveZoneState()
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!IsZoneStateValid(spawns)) {
|
||||||
|
LogInfo("No valid 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()));
|
||||||
|
|||||||
Reference in New Issue
Block a user