mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-23 13:12:28 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12eb838ee9 | |||
| 213fe6a9e9 | |||
| be6a5d5f50 | |||
| 1af29bd7b1 | |||
| ef945e6e99 | |||
| 9528c1e7fc | |||
| fd6e5f465d | |||
| d00125abe1 | |||
| 5d69235a4c | |||
| e93785f885 | |||
| 3c2545cfaf | |||
| 8d1a9efac9 | |||
| f6b18fb003 | |||
| 00e77f190c | |||
| 9cb72a6ba7 | |||
| 8203c034bf | |||
| 33ae51f56f | |||
| 30c39194a3 | |||
| 051ce3736f | |||
| 84708edccf |
@@ -1,3 +1,38 @@
|
||||
## [23.3.4] 3/14/2025
|
||||
|
||||
### Fixes
|
||||
|
||||
* Add check for simultaneous direct vendor and parcel Trader/Buyer Purchase ([#4778](https://github.com/EQEmu/Server/pull/4778)) @neckkola 2025-03-14
|
||||
* Fix for rare circumstance where NPC's would have 0 health on restore @Akkadius
|
||||
|
||||
## [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
|
||||
|
||||
### DynamicZones
|
||||
|
||||
* Bulk request dz member statuses on zone boot ([#4769](https://github.com/EQEmu/Server/pull/4769)) @hgtw 2025-03-11
|
||||
|
||||
### Zone
|
||||
|
||||
* Zone State Improvements (Continued) ([#4768](https://github.com/EQEmu/Server/pull/4768)) @Akkadius 2025-03-11
|
||||
|
||||
## [23.3.0] 3/8/2025
|
||||
|
||||
### Bots
|
||||
|
||||
@@ -955,6 +955,29 @@ bool Database::UpdateName(const std::string& old_name, const std::string& new_na
|
||||
return CharacterDataRepository::UpdateOne(*this, e);
|
||||
}
|
||||
|
||||
bool Database::UpdateNameByID(const int character_id, const std::string& new_name)
|
||||
{
|
||||
LogInfo("Renaming [{}] to [{}]", character_id, new_name);
|
||||
|
||||
auto l = CharacterDataRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`id` = {}",
|
||||
character_id
|
||||
)
|
||||
);
|
||||
|
||||
if (l.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& e = l.front();
|
||||
|
||||
e.name = new_name;
|
||||
|
||||
return CharacterDataRepository::UpdateOne(*this, e);
|
||||
}
|
||||
|
||||
bool Database::IsNameUsed(const std::string& name)
|
||||
{
|
||||
if (RuleB(Bots, Enabled)) {
|
||||
@@ -982,6 +1005,20 @@ bool Database::IsNameUsed(const std::string& name)
|
||||
return !character_data.empty();
|
||||
}
|
||||
|
||||
// Players cannot have the same name as a pet vanity name, or memory corruption occurs.
|
||||
bool Database::IsPetNameUsed(const std::string& name)
|
||||
{
|
||||
const auto& pet_name_data = CharacterPetNameRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`name` = '{}'",
|
||||
Strings::Escape(name)
|
||||
)
|
||||
);
|
||||
|
||||
return !pet_name_data.empty();
|
||||
}
|
||||
|
||||
uint32 Database::GetServerType()
|
||||
{
|
||||
const auto& l = VariablesRepository::GetWhere(*this, "`varname` = 'ServerType' LIMIT 1");
|
||||
|
||||
@@ -103,6 +103,7 @@ public:
|
||||
bool ReserveName(uint32 account_id, const std::string& name);
|
||||
bool SaveCharacterCreate(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp);
|
||||
bool UpdateName(const std::string& old_name, const std::string& new_name);
|
||||
bool UpdateNameByID(const int character_id, const std::string& new_name);
|
||||
bool CopyCharacter(
|
||||
const std::string& source_character_name,
|
||||
const std::string& destination_character_name,
|
||||
@@ -116,6 +117,7 @@ public:
|
||||
bool CheckGMIPs(const std::string& login_ip, uint32 account_id);
|
||||
bool CheckNameFilter(const std::string& name, bool surname = false);
|
||||
bool IsNameUsed(const std::string& name);
|
||||
bool IsPetNameUsed(const std::string& name);
|
||||
|
||||
uint32 GetAccountIDByChar(const std::string& name, uint32* character_id = 0);
|
||||
uint32 GetAccountIDByChar(uint32 character_id);
|
||||
|
||||
@@ -6845,7 +6845,7 @@ RENAME TABLE `expedition_lockouts` TO `dynamic_zone_lockouts`;
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
-- ✅ Drop old indexes
|
||||
-- Drop old indexes if exists
|
||||
DROP INDEX IF EXISTS `keys` ON `data_buckets`;
|
||||
DROP INDEX IF EXISTS `idx_npc_expires` ON `data_buckets`;
|
||||
DROP INDEX IF EXISTS `idx_bot_expires` ON `data_buckets`;
|
||||
@@ -6863,10 +6863,10 @@ ALTER TABLE `data_buckets`
|
||||
MODIFY COLUMN `npc_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `character_id`,
|
||||
MODIFY COLUMN `bot_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `npc_id`;
|
||||
|
||||
-- ✅ Create optimized unique index with `key` first
|
||||
-- Create optimized unique index with `key` first
|
||||
CREATE UNIQUE INDEX `keys` ON data_buckets (`key`, character_id, npc_id, bot_id, account_id, zone_id, instance_id);
|
||||
|
||||
-- ✅ Create indexes for just instance_id (instance deletion)
|
||||
-- Create indexes for just instance_id (instance deletion)
|
||||
CREATE INDEX idx_instance_id ON data_buckets (instance_id);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
@@ -6941,7 +6941,7 @@ CREATE TABLE `character_pet_name` (
|
||||
ManifestEntry{
|
||||
.version = 9310,
|
||||
.description = "2025_03_7_expand_horse_def.sql",
|
||||
.check = "SHOW COLUMNS FROM `horses` LIKE `helmtexture",
|
||||
.check = "SHOW COLUMNS FROM `horses` LIKE 'helmtexture'",
|
||||
.condition = "missing",
|
||||
.match = "TINYINT(2)",
|
||||
.sql = R"(
|
||||
@@ -6950,6 +6950,57 @@ ALTER TABLE `horses`
|
||||
)",
|
||||
.content_schema_update = true
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9311,
|
||||
.description = "2025_03_09_add_zone_state_is_zone_field.sql",
|
||||
.check = "SHOW COLUMNS FROM `zone_state_spawns` LIKE 'is_zone'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
ALTER TABLE `zone_state_spawns`
|
||||
ADD COLUMN `is_zone` tinyint(11) NULL DEFAULT 0 AFTER `is_corpse`;
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9312,
|
||||
.description = "2025_03_11_data_bucket_indexes.sql",
|
||||
.check = "SHOW INDEX FROM data_buckets",
|
||||
.condition = "missing",
|
||||
.match = "idx_zone_instance_expires",
|
||||
.sql = R"(
|
||||
DROP INDEX IF EXISTS `idx_zone_instance_expires` ON `data_buckets`;
|
||||
DROP INDEX IF EXISTS `idx_character_expires` ON `data_buckets`;
|
||||
DROP INDEX IF EXISTS `idx_bot_expires` ON `data_buckets`;
|
||||
ALTER TABLE data_buckets ADD INDEX idx_zone_instance_expires (zone_id, instance_id, expires);
|
||||
ALTER TABLE data_buckets ADD INDEX idx_character_expires (character_id, expires);
|
||||
ALTER TABLE data_buckets ADD INDEX idx_bot_expires (bot_id, expires);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9313,
|
||||
.description = "2025_03_11_zone_state_spawns.sql",
|
||||
.check = "SHOW INDEX FROM zone_state_spawns",
|
||||
.condition = "missing",
|
||||
.match = "idx_zone_instance",
|
||||
.sql = R"(
|
||||
ALTER TABLE zone_state_spawns ADD INDEX idx_zone_instance (zone_id, instance_id);
|
||||
ALTER TABLE zone_state_spawns ADD INDEX idx_instance_id (instance_id);
|
||||
)",
|
||||
.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
|
||||
// ManifestEntry{
|
||||
// .version = 9228,
|
||||
|
||||
@@ -287,6 +287,8 @@ N(OP_InstillDoubt),
|
||||
N(OP_InterruptCast),
|
||||
N(OP_InvokeChangePetName),
|
||||
N(OP_InvokeChangePetNameImmediate),
|
||||
N(OP_InvokeNameChangeImmediate),
|
||||
N(OP_InvokeNameChangeLazy),
|
||||
N(OP_ItemLinkClick),
|
||||
N(OP_ItemLinkResponse),
|
||||
N(OP_ItemLinkText),
|
||||
|
||||
@@ -5832,21 +5832,28 @@ struct ChangeSize_Struct
|
||||
/*16*/
|
||||
};
|
||||
|
||||
enum ChangeNameResponse : int {
|
||||
Denied = 0, // 5167: "You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name."
|
||||
Accepted = 1, // 5976: "Your request for a name change was successful."
|
||||
Timeout = -1, // 5977: "Your request for a name change has timed out. Please try again later."
|
||||
ServerError = -2, // 5978: "The server had an error while processing your name request. Please try again later."
|
||||
RateLimited = -3, // 5979: "You must wait longer before submitting another name request. Please try again in a few minutes."
|
||||
Ineligible = -4, // 5980: "Your character is not eligible for a name change."
|
||||
Pending = -5 // 5193: "You already have a name change pending. Please wait until it is fully processed before attempting another name change."
|
||||
};
|
||||
|
||||
struct AltChangeName_Struct {
|
||||
/*00*/ char new_name[64];
|
||||
/*40*/ char old_name[64];
|
||||
/*80*/ int response_code;
|
||||
};
|
||||
|
||||
struct ChangePetName_Struct {
|
||||
/*00*/ char new_pet_name[64];
|
||||
/*40*/ char pet_owner_name[64];
|
||||
/*80*/ int response_code;
|
||||
};
|
||||
|
||||
enum ChangePetNameResponse : int {
|
||||
Denied = 0, // 5167 You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name.
|
||||
Accepted = 1, // 5976 Your request for a name change was successful.
|
||||
Timeout = -3, // 5979 You must wait longer before submitting another name request. Please try again in a few minutes.
|
||||
NotEligible = -4, // 5980 Your character is not eligible for a name change.
|
||||
Pending = -5, // 5193 You already have a name change pending. Please wait until it is fully processed before attempting another name change.
|
||||
Unhandled = -1
|
||||
};
|
||||
|
||||
// New OpCode/Struct for SoD+
|
||||
struct GroupMakeLeader_Struct
|
||||
{
|
||||
|
||||
@@ -303,6 +303,14 @@ bool IpUtil::IsPortInUse(const std::string& ip, int port) {
|
||||
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{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
@@ -23,6 +23,7 @@ public:
|
||||
uint32_t zone_id;
|
||||
uint32_t instance_id;
|
||||
int8_t is_corpse;
|
||||
int8_t is_zone;
|
||||
int32_t decay_in_seconds;
|
||||
uint32_t npc_id;
|
||||
uint32_t spawn2_id;
|
||||
@@ -61,6 +62,7 @@ public:
|
||||
"zone_id",
|
||||
"instance_id",
|
||||
"is_corpse",
|
||||
"is_zone",
|
||||
"decay_in_seconds",
|
||||
"npc_id",
|
||||
"spawn2_id",
|
||||
@@ -95,6 +97,7 @@ public:
|
||||
"zone_id",
|
||||
"instance_id",
|
||||
"is_corpse",
|
||||
"is_zone",
|
||||
"decay_in_seconds",
|
||||
"npc_id",
|
||||
"spawn2_id",
|
||||
@@ -163,6 +166,7 @@ public:
|
||||
e.zone_id = 0;
|
||||
e.instance_id = 0;
|
||||
e.is_corpse = 0;
|
||||
e.is_zone = 0;
|
||||
e.decay_in_seconds = 0;
|
||||
e.npc_id = 0;
|
||||
e.spawn2_id = 0;
|
||||
@@ -227,30 +231,31 @@ public:
|
||||
e.zone_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
|
||||
e.instance_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.is_corpse = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
|
||||
e.decay_in_seconds = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
e.npc_id = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||
e.spawn2_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
|
||||
e.spawngroup_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.x = row[8] ? strtof(row[8], nullptr) : 0;
|
||||
e.y = row[9] ? strtof(row[9], nullptr) : 0;
|
||||
e.z = row[10] ? strtof(row[10], nullptr) : 0;
|
||||
e.heading = row[11] ? strtof(row[11], nullptr) : 0;
|
||||
e.respawn_time = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
|
||||
e.variance = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
|
||||
e.grid = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.current_waypoint = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
|
||||
e.path_when_zone_idle = row[16] ? static_cast<int16_t>(atoi(row[16])) : 0;
|
||||
e.condition_id = row[17] ? static_cast<uint16_t>(strtoul(row[17], nullptr, 10)) : 0;
|
||||
e.condition_min_value = row[18] ? static_cast<int16_t>(atoi(row[18])) : 0;
|
||||
e.enabled = row[19] ? static_cast<int16_t>(atoi(row[19])) : 1;
|
||||
e.anim = row[20] ? static_cast<uint16_t>(strtoul(row[20], nullptr, 10)) : 0;
|
||||
e.loot_data = row[21] ? row[21] : "";
|
||||
e.entity_variables = row[22] ? row[22] : "";
|
||||
e.buffs = row[23] ? row[23] : "";
|
||||
e.hp = row[24] ? strtoll(row[24], nullptr, 10) : 0;
|
||||
e.mana = row[25] ? strtoll(row[25], nullptr, 10) : 0;
|
||||
e.endurance = row[26] ? strtoll(row[26], nullptr, 10) : 0;
|
||||
e.created_at = strtoll(row[27] ? row[27] : "-1", nullptr, 10);
|
||||
e.is_zone = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
|
||||
e.decay_in_seconds = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
|
||||
e.npc_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
|
||||
e.spawn2_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.spawngroup_id = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
|
||||
e.x = row[9] ? strtof(row[9], nullptr) : 0;
|
||||
e.y = row[10] ? strtof(row[10], nullptr) : 0;
|
||||
e.z = row[11] ? strtof(row[11], nullptr) : 0;
|
||||
e.heading = row[12] ? strtof(row[12], nullptr) : 0;
|
||||
e.respawn_time = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
|
||||
e.variance = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.grid = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
|
||||
e.current_waypoint = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
|
||||
e.path_when_zone_idle = row[17] ? static_cast<int16_t>(atoi(row[17])) : 0;
|
||||
e.condition_id = row[18] ? static_cast<uint16_t>(strtoul(row[18], nullptr, 10)) : 0;
|
||||
e.condition_min_value = row[19] ? static_cast<int16_t>(atoi(row[19])) : 0;
|
||||
e.enabled = row[20] ? static_cast<int16_t>(atoi(row[20])) : 1;
|
||||
e.anim = row[21] ? static_cast<uint16_t>(strtoul(row[21], nullptr, 10)) : 0;
|
||||
e.loot_data = row[22] ? row[22] : "";
|
||||
e.entity_variables = row[23] ? row[23] : "";
|
||||
e.buffs = row[24] ? row[24] : "";
|
||||
e.hp = row[25] ? strtoll(row[25], nullptr, 10) : 0;
|
||||
e.mana = row[26] ? strtoll(row[26], nullptr, 10) : 0;
|
||||
e.endurance = row[27] ? strtoll(row[27], nullptr, 10) : 0;
|
||||
e.created_at = strtoll(row[28] ? row[28] : "-1", nullptr, 10);
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -287,30 +292,31 @@ public:
|
||||
v.push_back(columns[1] + " = " + std::to_string(e.zone_id));
|
||||
v.push_back(columns[2] + " = " + std::to_string(e.instance_id));
|
||||
v.push_back(columns[3] + " = " + std::to_string(e.is_corpse));
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.decay_in_seconds));
|
||||
v.push_back(columns[5] + " = " + std::to_string(e.npc_id));
|
||||
v.push_back(columns[6] + " = " + std::to_string(e.spawn2_id));
|
||||
v.push_back(columns[7] + " = " + std::to_string(e.spawngroup_id));
|
||||
v.push_back(columns[8] + " = " + std::to_string(e.x));
|
||||
v.push_back(columns[9] + " = " + std::to_string(e.y));
|
||||
v.push_back(columns[10] + " = " + std::to_string(e.z));
|
||||
v.push_back(columns[11] + " = " + std::to_string(e.heading));
|
||||
v.push_back(columns[12] + " = " + std::to_string(e.respawn_time));
|
||||
v.push_back(columns[13] + " = " + std::to_string(e.variance));
|
||||
v.push_back(columns[14] + " = " + std::to_string(e.grid));
|
||||
v.push_back(columns[15] + " = " + std::to_string(e.current_waypoint));
|
||||
v.push_back(columns[16] + " = " + std::to_string(e.path_when_zone_idle));
|
||||
v.push_back(columns[17] + " = " + std::to_string(e.condition_id));
|
||||
v.push_back(columns[18] + " = " + std::to_string(e.condition_min_value));
|
||||
v.push_back(columns[19] + " = " + std::to_string(e.enabled));
|
||||
v.push_back(columns[20] + " = " + std::to_string(e.anim));
|
||||
v.push_back(columns[21] + " = '" + Strings::Escape(e.loot_data) + "'");
|
||||
v.push_back(columns[22] + " = '" + Strings::Escape(e.entity_variables) + "'");
|
||||
v.push_back(columns[23] + " = '" + Strings::Escape(e.buffs) + "'");
|
||||
v.push_back(columns[24] + " = " + std::to_string(e.hp));
|
||||
v.push_back(columns[25] + " = " + std::to_string(e.mana));
|
||||
v.push_back(columns[26] + " = " + std::to_string(e.endurance));
|
||||
v.push_back(columns[27] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.is_zone));
|
||||
v.push_back(columns[5] + " = " + std::to_string(e.decay_in_seconds));
|
||||
v.push_back(columns[6] + " = " + std::to_string(e.npc_id));
|
||||
v.push_back(columns[7] + " = " + std::to_string(e.spawn2_id));
|
||||
v.push_back(columns[8] + " = " + std::to_string(e.spawngroup_id));
|
||||
v.push_back(columns[9] + " = " + std::to_string(e.x));
|
||||
v.push_back(columns[10] + " = " + std::to_string(e.y));
|
||||
v.push_back(columns[11] + " = " + std::to_string(e.z));
|
||||
v.push_back(columns[12] + " = " + std::to_string(e.heading));
|
||||
v.push_back(columns[13] + " = " + std::to_string(e.respawn_time));
|
||||
v.push_back(columns[14] + " = " + std::to_string(e.variance));
|
||||
v.push_back(columns[15] + " = " + std::to_string(e.grid));
|
||||
v.push_back(columns[16] + " = " + std::to_string(e.current_waypoint));
|
||||
v.push_back(columns[17] + " = " + std::to_string(e.path_when_zone_idle));
|
||||
v.push_back(columns[18] + " = " + std::to_string(e.condition_id));
|
||||
v.push_back(columns[19] + " = " + std::to_string(e.condition_min_value));
|
||||
v.push_back(columns[20] + " = " + std::to_string(e.enabled));
|
||||
v.push_back(columns[21] + " = " + std::to_string(e.anim));
|
||||
v.push_back(columns[22] + " = '" + Strings::Escape(e.loot_data) + "'");
|
||||
v.push_back(columns[23] + " = '" + Strings::Escape(e.entity_variables) + "'");
|
||||
v.push_back(columns[24] + " = '" + Strings::Escape(e.buffs) + "'");
|
||||
v.push_back(columns[25] + " = " + std::to_string(e.hp));
|
||||
v.push_back(columns[26] + " = " + std::to_string(e.mana));
|
||||
v.push_back(columns[27] + " = " + std::to_string(e.endurance));
|
||||
v.push_back(columns[28] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -336,6 +342,7 @@ public:
|
||||
v.push_back(std::to_string(e.zone_id));
|
||||
v.push_back(std::to_string(e.instance_id));
|
||||
v.push_back(std::to_string(e.is_corpse));
|
||||
v.push_back(std::to_string(e.is_zone));
|
||||
v.push_back(std::to_string(e.decay_in_seconds));
|
||||
v.push_back(std::to_string(e.npc_id));
|
||||
v.push_back(std::to_string(e.spawn2_id));
|
||||
@@ -393,6 +400,7 @@ public:
|
||||
v.push_back(std::to_string(e.zone_id));
|
||||
v.push_back(std::to_string(e.instance_id));
|
||||
v.push_back(std::to_string(e.is_corpse));
|
||||
v.push_back(std::to_string(e.is_zone));
|
||||
v.push_back(std::to_string(e.decay_in_seconds));
|
||||
v.push_back(std::to_string(e.npc_id));
|
||||
v.push_back(std::to_string(e.spawn2_id));
|
||||
@@ -454,30 +462,31 @@ public:
|
||||
e.zone_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
|
||||
e.instance_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.is_corpse = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
|
||||
e.decay_in_seconds = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
e.npc_id = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||
e.spawn2_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
|
||||
e.spawngroup_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.x = row[8] ? strtof(row[8], nullptr) : 0;
|
||||
e.y = row[9] ? strtof(row[9], nullptr) : 0;
|
||||
e.z = row[10] ? strtof(row[10], nullptr) : 0;
|
||||
e.heading = row[11] ? strtof(row[11], nullptr) : 0;
|
||||
e.respawn_time = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
|
||||
e.variance = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
|
||||
e.grid = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.current_waypoint = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
|
||||
e.path_when_zone_idle = row[16] ? static_cast<int16_t>(atoi(row[16])) : 0;
|
||||
e.condition_id = row[17] ? static_cast<uint16_t>(strtoul(row[17], nullptr, 10)) : 0;
|
||||
e.condition_min_value = row[18] ? static_cast<int16_t>(atoi(row[18])) : 0;
|
||||
e.enabled = row[19] ? static_cast<int16_t>(atoi(row[19])) : 1;
|
||||
e.anim = row[20] ? static_cast<uint16_t>(strtoul(row[20], nullptr, 10)) : 0;
|
||||
e.loot_data = row[21] ? row[21] : "";
|
||||
e.entity_variables = row[22] ? row[22] : "";
|
||||
e.buffs = row[23] ? row[23] : "";
|
||||
e.hp = row[24] ? strtoll(row[24], nullptr, 10) : 0;
|
||||
e.mana = row[25] ? strtoll(row[25], nullptr, 10) : 0;
|
||||
e.endurance = row[26] ? strtoll(row[26], nullptr, 10) : 0;
|
||||
e.created_at = strtoll(row[27] ? row[27] : "-1", nullptr, 10);
|
||||
e.is_zone = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
|
||||
e.decay_in_seconds = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
|
||||
e.npc_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
|
||||
e.spawn2_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.spawngroup_id = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
|
||||
e.x = row[9] ? strtof(row[9], nullptr) : 0;
|
||||
e.y = row[10] ? strtof(row[10], nullptr) : 0;
|
||||
e.z = row[11] ? strtof(row[11], nullptr) : 0;
|
||||
e.heading = row[12] ? strtof(row[12], nullptr) : 0;
|
||||
e.respawn_time = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
|
||||
e.variance = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.grid = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
|
||||
e.current_waypoint = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
|
||||
e.path_when_zone_idle = row[17] ? static_cast<int16_t>(atoi(row[17])) : 0;
|
||||
e.condition_id = row[18] ? static_cast<uint16_t>(strtoul(row[18], nullptr, 10)) : 0;
|
||||
e.condition_min_value = row[19] ? static_cast<int16_t>(atoi(row[19])) : 0;
|
||||
e.enabled = row[20] ? static_cast<int16_t>(atoi(row[20])) : 1;
|
||||
e.anim = row[21] ? static_cast<uint16_t>(strtoul(row[21], nullptr, 10)) : 0;
|
||||
e.loot_data = row[22] ? row[22] : "";
|
||||
e.entity_variables = row[23] ? row[23] : "";
|
||||
e.buffs = row[24] ? row[24] : "";
|
||||
e.hp = row[25] ? strtoll(row[25], nullptr, 10) : 0;
|
||||
e.mana = row[26] ? strtoll(row[26], nullptr, 10) : 0;
|
||||
e.endurance = row[27] ? strtoll(row[27], nullptr, 10) : 0;
|
||||
e.created_at = strtoll(row[28] ? row[28] : "-1", nullptr, 10);
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -506,30 +515,31 @@ public:
|
||||
e.zone_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
|
||||
e.instance_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
|
||||
e.is_corpse = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
|
||||
e.decay_in_seconds = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
|
||||
e.npc_id = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||
e.spawn2_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
|
||||
e.spawngroup_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.x = row[8] ? strtof(row[8], nullptr) : 0;
|
||||
e.y = row[9] ? strtof(row[9], nullptr) : 0;
|
||||
e.z = row[10] ? strtof(row[10], nullptr) : 0;
|
||||
e.heading = row[11] ? strtof(row[11], nullptr) : 0;
|
||||
e.respawn_time = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
|
||||
e.variance = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
|
||||
e.grid = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.current_waypoint = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
|
||||
e.path_when_zone_idle = row[16] ? static_cast<int16_t>(atoi(row[16])) : 0;
|
||||
e.condition_id = row[17] ? static_cast<uint16_t>(strtoul(row[17], nullptr, 10)) : 0;
|
||||
e.condition_min_value = row[18] ? static_cast<int16_t>(atoi(row[18])) : 0;
|
||||
e.enabled = row[19] ? static_cast<int16_t>(atoi(row[19])) : 1;
|
||||
e.anim = row[20] ? static_cast<uint16_t>(strtoul(row[20], nullptr, 10)) : 0;
|
||||
e.loot_data = row[21] ? row[21] : "";
|
||||
e.entity_variables = row[22] ? row[22] : "";
|
||||
e.buffs = row[23] ? row[23] : "";
|
||||
e.hp = row[24] ? strtoll(row[24], nullptr, 10) : 0;
|
||||
e.mana = row[25] ? strtoll(row[25], nullptr, 10) : 0;
|
||||
e.endurance = row[26] ? strtoll(row[26], nullptr, 10) : 0;
|
||||
e.created_at = strtoll(row[27] ? row[27] : "-1", nullptr, 10);
|
||||
e.is_zone = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
|
||||
e.decay_in_seconds = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
|
||||
e.npc_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
|
||||
e.spawn2_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.spawngroup_id = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
|
||||
e.x = row[9] ? strtof(row[9], nullptr) : 0;
|
||||
e.y = row[10] ? strtof(row[10], nullptr) : 0;
|
||||
e.z = row[11] ? strtof(row[11], nullptr) : 0;
|
||||
e.heading = row[12] ? strtof(row[12], nullptr) : 0;
|
||||
e.respawn_time = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
|
||||
e.variance = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
|
||||
e.grid = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
|
||||
e.current_waypoint = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
|
||||
e.path_when_zone_idle = row[17] ? static_cast<int16_t>(atoi(row[17])) : 0;
|
||||
e.condition_id = row[18] ? static_cast<uint16_t>(strtoul(row[18], nullptr, 10)) : 0;
|
||||
e.condition_min_value = row[19] ? static_cast<int16_t>(atoi(row[19])) : 0;
|
||||
e.enabled = row[20] ? static_cast<int16_t>(atoi(row[20])) : 1;
|
||||
e.anim = row[21] ? static_cast<uint16_t>(strtoul(row[21], nullptr, 10)) : 0;
|
||||
e.loot_data = row[22] ? row[22] : "";
|
||||
e.entity_variables = row[23] ? row[23] : "";
|
||||
e.buffs = row[24] ? row[24] : "";
|
||||
e.hp = row[25] ? strtoll(row[25], nullptr, 10) : 0;
|
||||
e.mana = row[26] ? strtoll(row[26], nullptr, 10) : 0;
|
||||
e.endurance = row[27] ? strtoll(row[27], nullptr, 10) : 0;
|
||||
e.created_at = strtoll(row[28] ? row[28] : "-1", nullptr, 10);
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -608,6 +618,7 @@ public:
|
||||
v.push_back(std::to_string(e.zone_id));
|
||||
v.push_back(std::to_string(e.instance_id));
|
||||
v.push_back(std::to_string(e.is_corpse));
|
||||
v.push_back(std::to_string(e.is_zone));
|
||||
v.push_back(std::to_string(e.decay_in_seconds));
|
||||
v.push_back(std::to_string(e.npc_id));
|
||||
v.push_back(std::to_string(e.spawn2_id));
|
||||
@@ -658,6 +669,7 @@ public:
|
||||
v.push_back(std::to_string(e.zone_id));
|
||||
v.push_back(std::to_string(e.instance_id));
|
||||
v.push_back(std::to_string(e.is_corpse));
|
||||
v.push_back(std::to_string(e.is_zone));
|
||||
v.push_back(std::to_string(e.decay_in_seconds));
|
||||
v.push_back(std::to_string(e.npc_id));
|
||||
v.push_back(std::to_string(e.spawn2_id));
|
||||
|
||||
@@ -5,9 +5,77 @@
|
||||
#include "../strings.h"
|
||||
#include "base/base_zone_state_spawns_repository.h"
|
||||
|
||||
class ZoneStateSpawnsRepository: public BaseZoneStateSpawnsRepository {
|
||||
class ZoneStateSpawnsRepository : public BaseZoneStateSpawnsRepository {
|
||||
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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
+3
-1
@@ -231,6 +231,7 @@ RULE_INT(Character, MendAlwaysSucceedValue, 199, "Value at which mend will alway
|
||||
RULE_BOOL(Character, SneakAlwaysSucceedOver100, false, "When sneak skill is over 100, always succeed sneak/hide. Default: false")
|
||||
RULE_INT(Character, BandolierSwapDelay, 0, "Bandolier swap delay in milliseconds, default is 0")
|
||||
RULE_BOOL(Character, EnableHackedFastCampForGM, false, "Enables hacked fast camp for GM clients, if the GM doesn't have a hacked client they'll camp like normal")
|
||||
RULE_BOOL(Character, AlwaysAllowNameChange, false, "Enable this option to allow /changename to work without enabling a name change via scripts.")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Mercs)
|
||||
@@ -376,6 +377,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, 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_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_CATEGORY_END()
|
||||
|
||||
@@ -834,7 +836,7 @@ RULE_INT(Bots, MinGroupCureTargets, 3, "Minimum number of targets in valid range
|
||||
RULE_INT(Bots, MinTargetsForAESpell, 3, "Minimum number of targets in valid range that are required for an AE spell to cast. Default 3.")
|
||||
RULE_INT(Bots, MinTargetsForGroupSpell, 3, "Minimum number of targets in valid range that are required for an group spell to cast. Default 3.")
|
||||
RULE_BOOL(Bots, AllowBuffingHealingFamiliars, false, "Determines if bots are allowed to buff and heal familiars. Default false.")
|
||||
RULE_BOOL(Bots, RunSpellTypeChecksOnSpawn, false, "This will run a serious of checks on spell types and output errors to LogBotSpellTypeChecks")
|
||||
RULE_BOOL(Bots, RunSpellTypeChecksOnBoot, false, "This will run a series of checks to find potential errors in your bot_spells_entries table on boot and output to LogBotSpellTypeChecks")
|
||||
RULE_BOOL(Bots, UseParentSpellTypeForChecks, true, "This will check only the parent instead of AE/Group/Pet types (ex: AENukes/AERains/PBAENukes fall under Nukes or PetBuffs fall under buffs) when RunSpellTypeChecksOnSpawn fires")
|
||||
RULE_BOOL(Bots, AllowForcedCastsBySpellID, true, "If enabled, players can use ^cast spellid # to cast a specific spell by ID that is in their spell list")
|
||||
RULE_BOOL(Bots, AllowCastAAs, true, "If enabled, players can use ^cast aa to cast a clickable AA")
|
||||
|
||||
@@ -196,6 +196,7 @@
|
||||
#define ServerOP_DzSaveInvite 0x0466
|
||||
#define ServerOP_DzRequestInvite 0x0467
|
||||
#define ServerOP_DzMakeLeader 0x0468
|
||||
#define ServerOP_DzGetBulkMemberStatuses 0x0469
|
||||
|
||||
#define ServerOP_LSInfo 0x1000
|
||||
#define ServerOP_LSStatus 0x1001
|
||||
@@ -1555,6 +1556,13 @@ struct ServerDzMemberStatuses_Struct {
|
||||
ServerDzMemberStatusEntry_Struct entries[0];
|
||||
};
|
||||
|
||||
struct ServerDzCerealData_Struct {
|
||||
uint16_t zone_id;
|
||||
uint16_t inst_id;
|
||||
uint32_t cereal_size;
|
||||
char cereal_data[1];
|
||||
};
|
||||
|
||||
struct ServerDzMovePC_Struct {
|
||||
uint32 dz_id;
|
||||
uint16 sender_zone_id;
|
||||
|
||||
+49
-37
@@ -1456,41 +1456,42 @@ bool IsCompleteHealSpell(uint16 spell_id)
|
||||
}
|
||||
|
||||
bool IsFastHealSpell(uint16 spell_id) {
|
||||
spell_id = (
|
||||
IsEffectInSpell(spell_id, SE_CurrentHP) ?
|
||||
spell_id :
|
||||
GetSpellTriggerSpellID(spell_id, SE_CurrentHP)
|
||||
);
|
||||
spell_id = (
|
||||
IsEffectInSpell(spell_id, SE_CurrentHP) ?
|
||||
spell_id :
|
||||
GetSpellTriggerSpellID(spell_id, SE_CurrentHP)
|
||||
);
|
||||
|
||||
if (!spell_id) {
|
||||
spell_id = (
|
||||
IsEffectInSpell(spell_id, SE_CurrentHPOnce) ?
|
||||
spell_id :
|
||||
GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce)
|
||||
);
|
||||
}
|
||||
if (!spell_id) {
|
||||
spell_id = (
|
||||
IsEffectInSpell(spell_id, SE_CurrentHPOnce) ?
|
||||
spell_id :
|
||||
GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce)
|
||||
);
|
||||
}
|
||||
|
||||
if (spell_id && IsValidSpell(spell_id)) {
|
||||
if (
|
||||
spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME &&
|
||||
spells[spell_id].good_effect &&
|
||||
!IsGroupSpell(spell_id)
|
||||
) {
|
||||
for (int i = 0; i < EFFECT_COUNT; i++) {
|
||||
if (
|
||||
spells[spell_id].base_value[i] > 0 &&
|
||||
(
|
||||
spells[spell_id].effect_id[i] == SE_CurrentHP ||
|
||||
spells[spell_id].effect_id[i] == SE_CurrentHPOnce
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (IsValidSpell(spell_id)) {
|
||||
if (
|
||||
spell_id != SPELL_MINOR_HEALING &&
|
||||
(spells[spell_id].cast_time > MAX_VERY_FAST_HEAL_CASTING_TIME && spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME) &&
|
||||
spells[spell_id].good_effect &&
|
||||
!IsGroupSpell(spell_id)
|
||||
) {
|
||||
for (int i = 0; i < EFFECT_COUNT; i++) {
|
||||
if (
|
||||
spells[spell_id].base_value[i] > 0 &&
|
||||
(
|
||||
spells[spell_id].effect_id[i] == SE_CurrentHP ||
|
||||
spells[spell_id].effect_id[i] == SE_CurrentHPOnce
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsVeryFastHealSpell(uint16 spell_id)
|
||||
@@ -1509,8 +1510,9 @@ bool IsVeryFastHealSpell(uint16 spell_id)
|
||||
);
|
||||
}
|
||||
|
||||
if (spell_id) {
|
||||
if (IsValidSpell(spell_id)) {
|
||||
if (
|
||||
spell_id != SPELL_MINOR_HEALING &&
|
||||
spells[spell_id].cast_time <= MAX_VERY_FAST_HEAL_CASTING_TIME &&
|
||||
spells[spell_id].good_effect &&
|
||||
!IsGroupSpell(spell_id)
|
||||
@@ -1548,8 +1550,13 @@ bool IsRegularSingleTargetHealSpell(uint16 spell_id)
|
||||
);
|
||||
}
|
||||
|
||||
if (spell_id) {
|
||||
if (IsValidSpell(spell_id)) {
|
||||
if (spell_id == SPELL_MINOR_HEALING) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
spells[spell_id].cast_time > MAX_FAST_HEAL_CASTING_TIME &&
|
||||
spells[spell_id].target_type == ST_Target &&
|
||||
!IsCompleteHealSpell(spell_id) &&
|
||||
!IsHealOverTimeSpell(spell_id) &&
|
||||
@@ -1589,9 +1596,14 @@ bool IsRegularPetHealSpell(uint16 spell_id)
|
||||
);
|
||||
}
|
||||
|
||||
if (spell_id && IsValidSpell(spell_id)) {
|
||||
if (IsValidSpell(spell_id)) {
|
||||
if (spell_id == SPELL_MINOR_HEALING) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
(spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_Undead) &&
|
||||
spells[spell_id].cast_time > MAX_FAST_HEAL_CASTING_TIME &&
|
||||
(spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_SummonedPet) &&
|
||||
!IsCompleteHealSpell(spell_id) &&
|
||||
!IsHealOverTimeSpell(spell_id) &&
|
||||
!IsGroupSpell(spell_id)
|
||||
@@ -1630,7 +1642,7 @@ bool IsRegularGroupHealSpell(uint16 spell_id)
|
||||
);
|
||||
}
|
||||
|
||||
if (spell_id) {
|
||||
if (IsValidSpell(spell_id)) {
|
||||
if (
|
||||
IsGroupSpell(spell_id) &&
|
||||
!IsCompleteHealSpell(spell_id) &&
|
||||
|
||||
@@ -215,6 +215,7 @@
|
||||
#define SPELL_AMPLIFICATION 2603
|
||||
#define SPELL_DIVINE_REZ 2738
|
||||
#define SPELL_NATURES_RECOVERY 2520
|
||||
#define SPELL_MINOR_HEALING 200
|
||||
#define SPELL_ADRENALINE_SWELL 14445
|
||||
#define SPELL_ADRENALINE_SWELL_RK2 14446
|
||||
#define SPELL_ADRENALINE_SWELL_RK3 14447
|
||||
|
||||
+12
-252
@@ -1,4 +1,5 @@
|
||||
#include "spdat.h"
|
||||
#include "../zone/bot.h"
|
||||
|
||||
bool IsBotSpellTypeDetrimental(uint16 spell_type) {
|
||||
switch (spell_type) {
|
||||
@@ -417,264 +418,23 @@ uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id) {
|
||||
return UINT16_MAX;
|
||||
}
|
||||
|
||||
uint16 correct_type = UINT16_MAX;
|
||||
SPDat_Spell_Struct spell = spells[spell_id];
|
||||
std::string teleport_zone = spell.teleport_zone;
|
||||
uint16 correct_type = spell_type;
|
||||
|
||||
if (IsCharmSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Charm;
|
||||
}
|
||||
else if (IsFearSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Fear;
|
||||
}
|
||||
else if (IsEffectInSpell(spell_id, SE_Revive)) {
|
||||
correct_type = BotSpellTypes::Resurrect;
|
||||
}
|
||||
else if (IsHarmonySpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Lull;
|
||||
}
|
||||
else if (
|
||||
teleport_zone.compare("") &&
|
||||
!IsEffectInSpell(spell_id, SE_GateToHomeCity) &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
|
||||
) {
|
||||
correct_type = BotSpellTypes::Teleport;
|
||||
}
|
||||
else if (
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_Succor)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Succor;
|
||||
}
|
||||
else if (IsEffectInSpell(spell_id, SE_BindAffinity)) {
|
||||
correct_type = BotSpellTypes::BindAffinity;
|
||||
}
|
||||
else if (IsEffectInSpell(spell_id, SE_Identify)) {
|
||||
correct_type = BotSpellTypes::Identify;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::Levitate &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_Levitate)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Levitate;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::Rune &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune))
|
||||
) {
|
||||
correct_type = BotSpellTypes::Rune;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::WaterBreathing &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_WaterBreathing)
|
||||
) {
|
||||
correct_type = BotSpellTypes::WaterBreathing;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::Size &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))
|
||||
) {
|
||||
correct_type = BotSpellTypes::Size;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::Invisibility &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id))
|
||||
) {
|
||||
correct_type = BotSpellTypes::Invisibility;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::MovementSpeed &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_MovementSpeed)
|
||||
) {
|
||||
correct_type = BotSpellTypes::MovementSpeed;
|
||||
}
|
||||
else if (
|
||||
!teleport_zone.compare("") &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_Translocate) || IsEffectInSpell(spell_id, SE_GateToHomeCity))
|
||||
) {
|
||||
correct_type = BotSpellTypes::SendHome;
|
||||
}
|
||||
else if (IsEffectInSpell(spell_id, SE_SummonCorpse)) {
|
||||
correct_type = BotSpellTypes::SummonCorpse;
|
||||
}
|
||||
if (!Bot::IsValidSpellTypeBySpellID(spell_type, spell_id)) {
|
||||
correct_type = UINT16_MAX;
|
||||
|
||||
if (correct_type == UINT16_MAX) {
|
||||
if (
|
||||
IsSummonPetSpell(spell_id) ||
|
||||
IsEffectInSpell(spell_id, SE_TemporaryPets)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Pet;
|
||||
}
|
||||
else if (IsMesmerizeSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Mez;
|
||||
}
|
||||
else if (IsEscapeSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Escape;
|
||||
}
|
||||
else if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_Root)
|
||||
) {
|
||||
if (IsAnyAESpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::AERoot;
|
||||
}
|
||||
else {
|
||||
correct_type = BotSpellTypes::Root;
|
||||
}
|
||||
}
|
||||
else if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
IsLifetapSpell(spell_id)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Lifetap;
|
||||
}
|
||||
else if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_MovementSpeed)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Snare;
|
||||
}
|
||||
else if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
(IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id))
|
||||
) {
|
||||
correct_type = BotSpellTypes::DOT;
|
||||
}
|
||||
else if (IsDispelSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Dispel;
|
||||
}
|
||||
else if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
IsSlowSpell(spell_id)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Slow;
|
||||
}
|
||||
else if (
|
||||
IsDebuffSpell(spell_id) &&
|
||||
!IsHateReduxSpell(spell_id) &&
|
||||
!IsHateSpell(spell_id)
|
||||
) {
|
||||
correct_type = BotSpellTypes::Debuff;
|
||||
}
|
||||
else if (IsHateReduxSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::HateRedux;
|
||||
}
|
||||
else if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
IsHateSpell(spell_id)
|
||||
) {
|
||||
correct_type = BotSpellTypes::HateLine;
|
||||
}
|
||||
else if (
|
||||
IsBuffSpell(spell_id) &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsBardSong(spell_id)
|
||||
) {
|
||||
if (
|
||||
spell_type == BotSpellTypes::InCombatBuffSong ||
|
||||
spell_type == BotSpellTypes::OutOfCombatBuffSong ||
|
||||
spell_type == BotSpellTypes::PreCombatBuffSong
|
||||
) {
|
||||
correct_type = spell_type;
|
||||
}
|
||||
else {
|
||||
correct_type = BotSpellTypes::OutOfCombatBuffSong;
|
||||
}
|
||||
}
|
||||
else if (
|
||||
!IsBardSong(spell_id) &&
|
||||
(
|
||||
(IsSelfConversionSpell(spell_id) && spell.buff_duration < 1) ||
|
||||
(spell_type == BotSpellTypes::InCombatBuff && IsAnyBuffSpell(spell_id))
|
||||
)
|
||||
) {
|
||||
correct_type = BotSpellTypes::InCombatBuff;
|
||||
}
|
||||
else if (
|
||||
spell_type == BotSpellTypes::PreCombatBuff &&
|
||||
IsAnyBuffSpell(spell_id) &&
|
||||
!IsBardSong(spell_id)
|
||||
) {
|
||||
correct_type = BotSpellTypes::PreCombatBuff;
|
||||
}
|
||||
else if (
|
||||
(IsCureSpell(spell_id) && spell_type == BotSpellTypes::Cure) ||
|
||||
(IsCureSpell(spell_id) && !IsAnyHealSpell(spell_id))
|
||||
) {
|
||||
correct_type = BotSpellTypes::Cure;
|
||||
}
|
||||
else if (IsAnyNukeOrStunSpell(spell_id)) {
|
||||
if (IsAnyAESpell(spell_id)) {
|
||||
if (IsAERainSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::AERains;
|
||||
}
|
||||
else if (IsPBAENukeSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::PBAENuke;
|
||||
}
|
||||
else if (IsStunSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::AEStun;
|
||||
}
|
||||
else {
|
||||
correct_type = BotSpellTypes::AENukes;
|
||||
}
|
||||
}
|
||||
else if (IsStunSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Stun;
|
||||
}
|
||||
else {
|
||||
correct_type = BotSpellTypes::Nuke;
|
||||
}
|
||||
}
|
||||
else if (IsAnyHealSpell(spell_id)) {
|
||||
if (IsGroupSpell(spell_id)) {
|
||||
if (IsGroupCompleteHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::GroupCompleteHeals;
|
||||
}
|
||||
else if (IsGroupHealOverTimeSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::GroupHoTHeals;
|
||||
}
|
||||
else if (IsRegularGroupHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::GroupHeals;
|
||||
}
|
||||
auto start = std::min({ BotSpellTypes::START, BotSpellTypes::COMMANDED_START, BotSpellTypes::DISCIPLINE_START });
|
||||
auto end = std::max({ BotSpellTypes::END, BotSpellTypes::COMMANDED_END, BotSpellTypes::DISCIPLINE_END });
|
||||
|
||||
return correct_type;
|
||||
for (int i = end; i >= start; --i) {
|
||||
if (!Bot::IsValidBotSpellType(i) || i == BotSpellTypes::InCombatBuff) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsVeryFastHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::VeryFastHeals;
|
||||
}
|
||||
else if (IsFastHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::FastHeals;
|
||||
}
|
||||
else if (IsCompleteHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::CompleteHeal;
|
||||
}
|
||||
else if (IsHealOverTimeSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::HoTHeals;
|
||||
}
|
||||
else if (IsRegularSingleTargetHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::RegularHeal;
|
||||
}
|
||||
else if (IsRegularPetHealSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::RegularHeal;
|
||||
}
|
||||
}
|
||||
else if (IsAnyBuffSpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::Buff;
|
||||
if (Bot::IsValidSpellTypeBySpellID(i, spell_id)) {
|
||||
correct_type = i;
|
||||
|
||||
if (IsResistanceOnlySpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::ResistBuffs;
|
||||
}
|
||||
else if (IsDamageShieldOnlySpell(spell_id)) {
|
||||
correct_type = BotSpellTypes::DamageShields;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@
|
||||
|
||||
// Build variables
|
||||
// these get injected during the build pipeline
|
||||
#define CURRENT_VERSION "23.3.0-dev" // always append -dev to the current version for custom-builds
|
||||
#define CURRENT_VERSION "23.3.4-dev" // always append -dev to the current version for custom-builds
|
||||
#define LOGIN_VERSION "0.8.0"
|
||||
#define COMPILE_DATE __DATE__
|
||||
#define COMPILE_TIME __TIME__
|
||||
@@ -42,7 +42,7 @@
|
||||
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
||||
*/
|
||||
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9310
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9314
|
||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
|
||||
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eqemu-server",
|
||||
"version": "23.3.0",
|
||||
"version": "23.3.4",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EQEmu/Server.git"
|
||||
|
||||
@@ -746,3 +746,6 @@ OP_TradeSkillRecipeInspect=0x4f7e
|
||||
OP_InvokeChangePetNameImmediate=0x046d
|
||||
OP_InvokeChangePetName=0x4506
|
||||
OP_ChangePetName=0x5dab
|
||||
|
||||
OP_InvokeNameChangeImmediate=0x4fe2
|
||||
OP_InvokeNameChangeLazy=0x2f2e
|
||||
|
||||
@@ -10,7 +10,7 @@ require (
|
||||
require (
|
||||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/crypto v0.35.0 // indirect
|
||||
golang.org/x/net v0.36.0 // 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=
|
||||
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.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
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/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "zoneserver.h"
|
||||
#include "../common/rulesys.h"
|
||||
#include "../common/repositories/dynamic_zone_lockouts_repository.h"
|
||||
#include <cereal/types/utility.hpp>
|
||||
|
||||
extern ClientList client_list;
|
||||
extern ZSList zoneserver_list;
|
||||
@@ -169,6 +170,33 @@ void DynamicZoneManager::LoadTemplates()
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicZoneManager::SendBulkMemberStatuses(uint32_t zone_id, uint16_t inst_id)
|
||||
{
|
||||
std::vector<std::pair<uint32_t, std::vector<DynamicZoneMember>>> dzs;
|
||||
dzs.reserve(dynamic_zone_cache.size());
|
||||
|
||||
for (const auto& [dz_id, dz] : dynamic_zone_cache)
|
||||
{
|
||||
dzs.emplace_back(dz_id, dz->GetMembers());
|
||||
}
|
||||
|
||||
std::ostringstream ss;
|
||||
{
|
||||
cereal::BinaryOutputArchive archive(ss);
|
||||
archive(dzs);
|
||||
}
|
||||
|
||||
std::string_view sv = ss.view();
|
||||
|
||||
size_t size = sizeof(ServerDzCerealData_Struct) + sv.size();
|
||||
ServerPacket pack(ServerOP_DzGetBulkMemberStatuses, static_cast<uint32_t>(size));
|
||||
auto buf = reinterpret_cast<ServerDzCerealData_Struct*>(pack.pBuffer);
|
||||
buf->cereal_size = static_cast<uint32_t>(sv.size());
|
||||
memcpy(buf->cereal_data, sv.data(), sv.size());
|
||||
|
||||
zoneserver_list.SendPacket(zone_id, inst_id, &pack);
|
||||
}
|
||||
|
||||
void DynamicZoneManager::HandleZoneMessage(ServerPacket* pack)
|
||||
{
|
||||
switch (pack->opcode)
|
||||
@@ -338,6 +366,15 @@ void DynamicZoneManager::HandleZoneMessage(ServerPacket* pack)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ServerOP_DzGetBulkMemberStatuses:
|
||||
{
|
||||
auto buf = reinterpret_cast<ServerDzCerealData_Struct*>(pack->pBuffer);
|
||||
if (buf->zone_id != 0 && !dynamic_zone_cache.empty())
|
||||
{
|
||||
SendBulkMemberStatuses(buf->zone_id, buf->inst_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ServerOP_DzUpdateMemberStatus:
|
||||
{
|
||||
auto buf = reinterpret_cast<ServerDzMemberStatus_Struct*>(pack->pBuffer);
|
||||
|
||||
@@ -30,6 +30,8 @@ public:
|
||||
std::unordered_map<uint32_t, std::unique_ptr<DynamicZone>> dynamic_zone_cache;
|
||||
|
||||
private:
|
||||
void SendBulkMemberStatuses(uint32_t zone_id, uint16_t inst_id);
|
||||
|
||||
Timer m_process_throttle_timer{};
|
||||
std::unordered_map<uint32_t, DynamicZoneTemplatesRepository::DynamicZoneTemplates> m_dz_templates;
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "../common/zone_store.h"
|
||||
#include "../common/path_manager.h"
|
||||
#include "../common/database/database_update.h"
|
||||
#include "../common/repositories/zone_state_spawns_repository.h"
|
||||
|
||||
extern ZSList zoneserver_list;
|
||||
extern WorldConfig Config;
|
||||
@@ -412,6 +413,11 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv)
|
||||
LogInfo("Cleaning up instance corpses");
|
||||
database.CleanupInstanceCorpses();
|
||||
|
||||
if (RuleB(Zone, StateSavingOnShutdown)) {
|
||||
ZoneStateSpawnsRepository::PurgeInvalidZoneStates(database);
|
||||
ZoneStateSpawnsRepository::PurgeOldZoneStates(database);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1480,6 +1480,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
case ServerOP_DzSwapMembers:
|
||||
case ServerOP_DzRemoveAllMembers:
|
||||
case ServerOP_DzGetMemberStatuses:
|
||||
case ServerOP_DzGetBulkMemberStatuses:
|
||||
case ServerOP_DzSetSecondsRemaining:
|
||||
case ServerOP_DzSetCompass:
|
||||
case ServerOP_DzSetSafeReturn:
|
||||
|
||||
@@ -3041,9 +3041,15 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
||||
if (!other)
|
||||
return;
|
||||
|
||||
if (other->IsDestroying())
|
||||
return;
|
||||
|
||||
if (other == this)
|
||||
return;
|
||||
|
||||
if (other->IsClient() && (other->CastToClient()->IsZoning() || other->CastToClient()->Connected() == false))
|
||||
return;
|
||||
|
||||
if (other->IsTrap())
|
||||
return;
|
||||
|
||||
|
||||
+293
-79
@@ -3578,8 +3578,16 @@ void Bot::Depop() {
|
||||
entity_list.RemoveFromHateLists(this);
|
||||
RemoveAllAuras();
|
||||
|
||||
if (HasPet())
|
||||
GetPet()->Depop();
|
||||
Mob* bot_pet = GetPet();
|
||||
|
||||
if (bot_pet) {
|
||||
if (bot_pet->Charmed()) {
|
||||
bot_pet->BuffFadeByEffect(SE_Charm);
|
||||
}
|
||||
else {
|
||||
bot_pet->Depop();
|
||||
}
|
||||
}
|
||||
|
||||
_botOwner = nullptr;
|
||||
_botOwnerCharacterID = 0;
|
||||
@@ -3702,11 +3710,6 @@ bool Bot::Spawn(Client* botCharacterOwner) {
|
||||
}
|
||||
}
|
||||
|
||||
if (RuleB(Bots, RunSpellTypeChecksOnSpawn)) {
|
||||
OwnerMessage("Running SpellType checks. There may be some spells that are mislabeled as incorrect. Use this as a loose guideline.");
|
||||
CheckBotSpells(); //This runs through a series of checks and outputs any spells that are set to the wrong spell type in the database
|
||||
}
|
||||
|
||||
if (IsBotRanged()) {
|
||||
ChangeBotRangedWeapons(true);
|
||||
}
|
||||
@@ -9790,6 +9793,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
|
||||
}
|
||||
|
||||
uint8 bot_class = GetClass();
|
||||
auto spell = spells[spell_id];
|
||||
|
||||
switch (spell_type) {
|
||||
case BotSpellTypes::Buff:
|
||||
@@ -9813,7 +9817,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
|
||||
case BotSpellTypes::SendHome:
|
||||
if (
|
||||
tar == this &&
|
||||
spells[spell_id].target_type == ST_TargetsTarget
|
||||
spell.target_type == ST_TargetsTarget
|
||||
) {
|
||||
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to target_type checks. Using {}'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName(), GetSpellTargetType(spell_id));
|
||||
return false;
|
||||
@@ -9843,7 +9847,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
|
||||
}
|
||||
|
||||
if (
|
||||
spells[spell_id].target_type == ST_Pet &&
|
||||
(spell.target_type == ST_Pet || spell.target_type == ST_SummonedPet) &&
|
||||
(
|
||||
!tar->IsPet() ||
|
||||
(
|
||||
@@ -11566,18 +11570,265 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsBotSpellTypeDetrimental(spell_type) && !IsDetrimentalSpell(spell_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsBotSpellTypeBeneficial(spell_type) && !IsBeneficialSpell(spell_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto spell = spells[spell_id];
|
||||
std::string teleport_zone = spell.teleport_zone;
|
||||
|
||||
switch (spell_type) {
|
||||
case BotSpellTypes::Buff:
|
||||
case BotSpellTypes::PetBuffs:
|
||||
if (
|
||||
IsResistanceOnlySpell(spell_id) ||
|
||||
IsDamageShieldOnlySpell(spell_id) ||
|
||||
IsDamageShieldAndResistSpell(spell_id)
|
||||
) {
|
||||
return false;
|
||||
case BotSpellTypes::Nuke:
|
||||
if (IsAnyNukeOrStunSpell(spell_id) && !IsStunSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
case BotSpellTypes::RegularHeal:
|
||||
case BotSpellTypes::PetRegularHeals:
|
||||
if (
|
||||
IsAnyHealSpell(spell_id) &&
|
||||
!IsVeryFastHealSpell(spell_id) &&
|
||||
!IsFastHealSpell(spell_id) &&
|
||||
!IsCompleteHealSpell(spell_id) &&
|
||||
!IsHealOverTimeSpell(spell_id) &&
|
||||
!IsBuffSpell(spell_id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Root:
|
||||
case BotSpellTypes::AERoot:
|
||||
if (IsDetrimentalSpell(spell_id) && IsEffectInSpell(spell_id, SE_Root)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Buff:
|
||||
case BotSpellTypes::PreCombatBuff:
|
||||
case BotSpellTypes::PetBuffs:
|
||||
if (
|
||||
IsBuffSpell(spell_id) &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
!IsBardSong(spell_id) &&
|
||||
!IsResistanceOnlySpell(spell_id) &&
|
||||
!IsDamageShieldOnlySpell(spell_id) &&
|
||||
!IsDamageShieldAndResistSpell(spell_id)
|
||||
) {
|
||||
if (
|
||||
spell_type != BotSpellTypes::PetBuffs &&
|
||||
(spell.target_type == ST_Pet || spell.target_type == ST_SummonedPet)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Escape:
|
||||
if (IsEscapeSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Pet:
|
||||
if (IsSummonPetSpell(spell_id) || IsEffectInSpell(spell_id, SE_TemporaryPets)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Lifetap:
|
||||
case BotSpellTypes::AELifetap:
|
||||
if (IsDetrimentalSpell(spell_id) && IsLifetapSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Snare:
|
||||
case BotSpellTypes::AESnare:
|
||||
if (IsDetrimentalSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::DOT:
|
||||
case BotSpellTypes::AEDoT:
|
||||
if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
(IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Dispel:
|
||||
case BotSpellTypes::AEDispel:
|
||||
if (IsDispelSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::InCombatBuff:
|
||||
if (
|
||||
!IsBardSong(spell_id) &&
|
||||
(
|
||||
(IsSelfConversionSpell(spell_id) && spell.buff_duration < 1) ||
|
||||
(spell_type == BotSpellTypes::InCombatBuff && IsAnyBuffSpell(spell_id))
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Mez:
|
||||
case BotSpellTypes::AEMez:
|
||||
if (IsMesmerizeSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Charm:
|
||||
if (IsCharmSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Slow:
|
||||
case BotSpellTypes::AESlow:
|
||||
if (IsDetrimentalSpell(spell_id) && IsSlowSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Debuff:
|
||||
case BotSpellTypes::AEDebuff:
|
||||
if (
|
||||
IsDebuffSpell(spell_id) &&
|
||||
!IsHateReduxSpell(spell_id) &&
|
||||
!IsHateSpell(spell_id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Cure:
|
||||
case BotSpellTypes::GroupCures:
|
||||
case BotSpellTypes::PetCures:
|
||||
if (IsCureSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Resurrect:
|
||||
if (IsEffectInSpell(spell_id, SE_Revive)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::HateRedux:
|
||||
if (IsHateReduxSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::InCombatBuffSong:
|
||||
case BotSpellTypes::OutOfCombatBuffSong:
|
||||
case BotSpellTypes::PreCombatBuffSong:
|
||||
if (
|
||||
IsBuffSpell(spell_id) &&
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsBardSong(spell_id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Fear:
|
||||
case BotSpellTypes::AEFear:
|
||||
if (IsFearSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Stun:
|
||||
case BotSpellTypes::AEStun:
|
||||
if (IsDetrimentalSpell(spell_id) && IsStunSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::HateLine:
|
||||
case BotSpellTypes::AEHateLine:
|
||||
if (IsDetrimentalSpell(spell_id) && IsHateSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::CompleteHeal:
|
||||
case BotSpellTypes::GroupCompleteHeals:
|
||||
case BotSpellTypes::PetCompleteHeals:
|
||||
if (IsCompleteHealSpell(spell_id) || IsGroupCompleteHealSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::FastHeals:
|
||||
case BotSpellTypes::PetFastHeals:
|
||||
if (IsFastHealSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::VeryFastHeals:
|
||||
case BotSpellTypes::PetVeryFastHeals:
|
||||
if (IsVeryFastHealSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::GroupHeals:
|
||||
if (IsRegularGroupHealSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::HoTHeals:
|
||||
case BotSpellTypes::GroupHoTHeals:
|
||||
case BotSpellTypes::PetHoTHeals:
|
||||
if (IsHealOverTimeSpell(spell_id) || IsGroupHealOverTimeSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::AENukes:
|
||||
if (
|
||||
IsDetrimentalSpell(spell_id) &&
|
||||
!IsAERainSpell(spell_id) &&
|
||||
!IsPBAENukeSpell(spell_id) &&
|
||||
!IsStunSpell(spell_id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::AERains:
|
||||
if (IsAERainNukeSpell(spell_id) && !IsStunSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::PBAENuke:
|
||||
if (IsPBAENukeSpell(spell_id) && !IsStunSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::ResistBuffs:
|
||||
case BotSpellTypes::PetResistBuffs:
|
||||
if (IsResistanceBuffSpell(spell_id)) {
|
||||
@@ -11587,44 +11838,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
return false;
|
||||
case BotSpellTypes::DamageShields:
|
||||
case BotSpellTypes::PetDamageShields:
|
||||
if (IsEffectInSpell(spell_id, SE_DamageShield)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::PBAENuke:
|
||||
if (
|
||||
IsPBAENukeSpell(spell_id) &&
|
||||
!IsStunSpell(spell_id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::AERains:
|
||||
if (
|
||||
IsAERainNukeSpell(spell_id) &&
|
||||
!IsStunSpell(spell_id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case BotSpellTypes::AEStun:
|
||||
case BotSpellTypes::Stun:
|
||||
if (IsStunSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::AENukes:
|
||||
case BotSpellTypes::Nuke:
|
||||
if (!IsStunSpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Lull:
|
||||
if (IsHarmonySpell(spell_id)) {
|
||||
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_DamageShield)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11632,13 +11846,18 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
case BotSpellTypes::Teleport:
|
||||
if (
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(
|
||||
IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate)
|
||||
)
|
||||
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Lull:
|
||||
case BotSpellTypes::AELull:
|
||||
if (IsHarmonySpell(spell_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Succor:
|
||||
if (
|
||||
@@ -11662,25 +11881,21 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Levitate:
|
||||
if (
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_Levitate)
|
||||
) {
|
||||
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Levitate)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Rune:
|
||||
if (
|
||||
IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) ||
|
||||
IsEffectInSpell(spell_id, SE_Rune)
|
||||
if (IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::WaterBreathing:
|
||||
if (IsEffectInSpell(spell_id, SE_WaterBreathing)) {
|
||||
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_WaterBreathing)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11688,29 +11903,22 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
case BotSpellTypes::Size:
|
||||
if (
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
(
|
||||
IsEffectInSpell(spell_id, SE_ModelSize) ||
|
||||
IsEffectInSpell(spell_id, SE_ChangeHeight)
|
||||
)
|
||||
(IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::Invisibility:
|
||||
if (
|
||||
IsEffectInSpell(spell_id, SE_SeeInvis) ||
|
||||
IsInvisibleSpell(spell_id)
|
||||
if (IsBeneficialSpell(spell_id) &&
|
||||
(IsEffectInSpell(spell_id, SE_SeeInvis) ||IsInvisibleSpell(spell_id))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
case BotSpellTypes::MovementSpeed:
|
||||
if (
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_MovementSpeed)
|
||||
) {
|
||||
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11718,7 +11926,13 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
||||
case BotSpellTypes::SendHome:
|
||||
if (
|
||||
IsBeneficialSpell(spell_id) &&
|
||||
IsEffectInSpell(spell_id, SE_GateToHomeCity)
|
||||
(
|
||||
IsEffectInSpell(spell_id, SE_GateToHomeCity) ||
|
||||
(
|
||||
teleport_zone.compare("") &&
|
||||
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
|
||||
)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -683,7 +683,6 @@ public:
|
||||
void SetSitManaPct(uint8 value) { _SitManaPct = value; }
|
||||
|
||||
// Spell lists
|
||||
void CheckBotSpells();
|
||||
std::list<BotSpellTypeOrder> GetSpellTypesPrioritized(uint8 priority_type);
|
||||
static uint16 GetParentSpellType(uint16 spell_type);
|
||||
static bool IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id);
|
||||
|
||||
@@ -220,6 +220,11 @@ int bot_command_init(void)
|
||||
std::vector<std::pair<std::string, uint8>> injected_bot_command_settings;
|
||||
std::vector<std::string> orphaned_bot_command_settings;
|
||||
|
||||
if (RuleB(Bots, RunSpellTypeChecksOnBoot)) {
|
||||
LogBotSpellTypeChecks("Running SpellType checks. There may be some spells that are mislabeled as incorrect. Use this as a loose guideline.");
|
||||
database.botdb.CheckBotSpells();
|
||||
}
|
||||
|
||||
database.botdb.MapCommandedSpellTypeMinLevels();
|
||||
|
||||
for (auto bcs_iter : bot_command_settings) {
|
||||
@@ -813,7 +818,7 @@ void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type)
|
||||
auto spell_type_itr = spell_map.find(spell_type);
|
||||
auto class_itr = spell_type_itr->second.find(i);
|
||||
const auto& spell_info = class_itr->second;
|
||||
|
||||
|
||||
if (spell_info.min_level < UINT8_MAX) {
|
||||
found = true;
|
||||
|
||||
|
||||
@@ -2523,6 +2523,142 @@ bool BotDatabase::DeleteBotBlockedBuffs(const uint32 bot_id)
|
||||
return true;
|
||||
}
|
||||
|
||||
void BotDatabase::CheckBotSpells() {
|
||||
auto spell_list = BotSpellsEntriesRepository::All(content_db);
|
||||
uint16 spell_id;
|
||||
SPDat_Spell_Struct spell;
|
||||
|
||||
for (const auto& s : spell_list) {
|
||||
if (!IsValidSpell(s.spell_id)) {
|
||||
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
spell = spells[s.spell_id];
|
||||
spell_id = spell.id;
|
||||
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id);
|
||||
}
|
||||
else {
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}.",
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||
s.npc_spells_id,
|
||||
s.minlevel
|
||||
);
|
||||
|
||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
spell_id,
|
||||
s.npc_spells_id,
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
s.minlevel,
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||
s.npc_spells_id
|
||||
);
|
||||
}
|
||||
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) {
|
||||
LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}.",
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||
s.npc_spells_id,
|
||||
s.minlevel
|
||||
);
|
||||
|
||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
spell_id,
|
||||
s.npc_spells_id,
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
s.minlevel,
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||
s.npc_spells_id
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}.",
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||
s.npc_spells_id,
|
||||
s.maxlevel
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
uint16 correct_type = GetCorrectBotSpellType(s.type, spell_id);
|
||||
|
||||
if (RuleB(Bots, UseParentSpellTypeForChecks)) {
|
||||
uint16 parent_type = Bot::GetParentSpellType(correct_type);
|
||||
|
||||
if (s.type == parent_type || s.type == correct_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (correct_type != parent_type) {
|
||||
correct_type = parent_type;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (IsPetBotSpellType(s.type)) {
|
||||
correct_type = GetPetBotSpellType(correct_type);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsPetBotSpellType(correct_type) && (spell.target_type != ST_Pet && spell.target_type != ST_SummonedPet)) {
|
||||
correct_type = Bot::GetParentSpellType(correct_type);
|
||||
}
|
||||
|
||||
if (correct_type == s.type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (correct_type == UINT16_MAX) {
|
||||
LogBotSpellTypeChecks(
|
||||
"{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown.",
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
Bot::GetSpellTypeNameByID(s.type),
|
||||
s.type
|
||||
);
|
||||
}
|
||||
else {
|
||||
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]",
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
Bot::GetSpellTypeNameByID(s.type),
|
||||
s.type,
|
||||
Bot::GetSpellTypeNameByID(correct_type),
|
||||
correct_type
|
||||
);
|
||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]",
|
||||
correct_type,
|
||||
spell_id,
|
||||
GetSpellName(spell_id),
|
||||
spell_id,
|
||||
Bot::GetSpellTypeNameByID(s.type),
|
||||
s.type,
|
||||
Bot::GetSpellTypeNameByID(correct_type),
|
||||
correct_type
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BotDatabase::MapCommandedSpellTypeMinLevels() {
|
||||
commanded_spell_type_min_levels.clear();
|
||||
|
||||
|
||||
@@ -130,6 +130,7 @@ public:
|
||||
bool SaveBotSettings(Mob* m);
|
||||
bool DeleteBotSettings(const uint32 bot_id);
|
||||
|
||||
void CheckBotSpells();
|
||||
void MapCommandedSpellTypeMinLevels();
|
||||
std::map<int32_t, std::map<int32_t, BotSpellTypesByClass>> GetCommandedSpellTypesMinLevels() { return commanded_spell_type_min_levels; }
|
||||
|
||||
|
||||
@@ -2865,132 +2865,3 @@ BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_typ
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void Bot::CheckBotSpells() {
|
||||
auto spell_list = BotSpellsEntriesRepository::All(content_db);
|
||||
uint16 spell_id;
|
||||
SPDat_Spell_Struct spell;
|
||||
uint16 correct_type;
|
||||
uint16 parent_type;
|
||||
|
||||
for (const auto& s : spell_list) {
|
||||
if (!IsValidSpell(s.spell_id)) {
|
||||
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
spell = spells[s.spell_id];
|
||||
spell_id = spell.id;
|
||||
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id);
|
||||
}
|
||||
else {
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}."
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
||||
, s.npc_spells_id
|
||||
, s.minlevel
|
||||
);
|
||||
|
||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]"
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, spell_id
|
||||
, s.npc_spells_id
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, s.minlevel
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
||||
, s.npc_spells_id
|
||||
);
|
||||
}
|
||||
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) {
|
||||
LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}."
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
||||
, s.npc_spells_id
|
||||
, s.minlevel
|
||||
);
|
||||
|
||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]"
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, spell_id
|
||||
, s.npc_spells_id
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, s.minlevel
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
||||
, s.npc_spells_id
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}."
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
||||
, s.npc_spells_id
|
||||
, s.maxlevel
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
correct_type = GetCorrectBotSpellType(s.type, spell_id);
|
||||
parent_type = GetParentSpellType(correct_type);
|
||||
|
||||
if (RuleB(Bots, UseParentSpellTypeForChecks)) {
|
||||
if (s.type == parent_type || s.type == correct_type) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (IsPetBotSpellType(s.type)) {
|
||||
correct_type = GetPetBotSpellType(correct_type);
|
||||
}
|
||||
}
|
||||
|
||||
if (correct_type == s.type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (correct_type == UINT16_MAX) {
|
||||
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown."
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, GetSpellTypeNameByID(s.type)
|
||||
, s.type
|
||||
);
|
||||
}
|
||||
else {
|
||||
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]"
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, GetSpellTypeNameByID(s.type)
|
||||
, s.type
|
||||
, GetSpellTypeNameByID(correct_type)
|
||||
, correct_type
|
||||
);
|
||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]"
|
||||
, correct_type
|
||||
, spell_id
|
||||
, GetSpellName(spell_id)
|
||||
, spell_id
|
||||
, GetSpellTypeNameByID(s.type)
|
||||
, s.type
|
||||
, GetSpellTypeNameByID(correct_type)
|
||||
, correct_type
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+86
-16
@@ -2549,40 +2549,59 @@ void Client::ChangeLastName(std::string last_name) {
|
||||
safe_delete(outapp);
|
||||
}
|
||||
|
||||
bool Client::ChangeFirstName(const char* in_firstname, const char* gmname)
|
||||
// Deprecated, this packet does not actually work in ROF2
|
||||
bool Client::ChangeFirstName(const std::string in_firstname, const std::string gmname)
|
||||
{
|
||||
// check duplicate name
|
||||
bool used_name = database.IsNameUsed((const char*) in_firstname);
|
||||
if (used_name) {
|
||||
if (!ChangeFirstName(in_firstname)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// update character_
|
||||
if(!database.UpdateName(GetName(), in_firstname))
|
||||
return false;
|
||||
|
||||
// update pp
|
||||
memset(m_pp.name, 0, sizeof(m_pp.name));
|
||||
snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname);
|
||||
strcpy(name, m_pp.name);
|
||||
Save();
|
||||
|
||||
// send name update packet
|
||||
auto outapp = new EQApplicationPacket(OP_GMNameChange, sizeof(GMName_Struct));
|
||||
GMName_Struct* gmn=(GMName_Struct*)outapp->pBuffer;
|
||||
strn0cpy(gmn->gmname,gmname,64);
|
||||
strn0cpy(gmn->gmname,gmname.c_str(),64);
|
||||
strn0cpy(gmn->oldname,GetName(),64);
|
||||
strn0cpy(gmn->newname,in_firstname,64);
|
||||
strn0cpy(gmn->newname,in_firstname.c_str(),64);
|
||||
gmn->unknown[0] = 1;
|
||||
gmn->unknown[1] = 1;
|
||||
gmn->unknown[2] = 1;
|
||||
entity_list.QueueClients(this, outapp, false);
|
||||
safe_delete(outapp);
|
||||
|
||||
// success
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Client::ChangeFirstName(const std::string in_firstname)
|
||||
{
|
||||
// check duplicate name
|
||||
bool used_name = database.IsNameUsed(in_firstname) || database.IsPetNameUsed(in_firstname);
|
||||
if (used_name || !database.CheckNameFilter(in_firstname, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// update character_
|
||||
if(!database.UpdateNameByID(CharacterID(), in_firstname))
|
||||
return false;
|
||||
|
||||
// Send Name Update to Clients
|
||||
SendRename(this, GetName(), in_firstname.c_str());
|
||||
SetName(in_firstname.c_str());
|
||||
|
||||
// update pp
|
||||
memset(m_pp.name, 0, sizeof(m_pp.name));
|
||||
snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname.c_str());
|
||||
strcpy(name, m_pp.name);
|
||||
Save();
|
||||
|
||||
// Update the active char in account table
|
||||
database.UpdateLiveChar(in_firstname, AccountID());
|
||||
|
||||
// finally, update the /who list
|
||||
UpdateWho();
|
||||
|
||||
// success
|
||||
ClearNameChange();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4737,6 +4756,57 @@ bool Client::KeyRingRemove(uint32 item_id)
|
||||
);
|
||||
}
|
||||
|
||||
bool Client::IsNameChangeAllowed() {
|
||||
if (RuleB(Character, AlwaysAllowNameChange)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto k = GetScopedBucketKeys();
|
||||
k.key = "name_change_allowed";
|
||||
|
||||
auto b = DataBucket::GetData(k);
|
||||
if (!b.value.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Client::ClearNameChange() {
|
||||
if (!IsNameChangeAllowed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto k = GetScopedBucketKeys();
|
||||
k.key = "name_change_allowed";
|
||||
|
||||
DataBucket::DeleteData(k);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Client::InvokeChangeNameWindow(bool immediate) {
|
||||
if (!IsNameChangeAllowed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet_op = immediate ? OP_InvokeNameChangeImmediate : OP_InvokeNameChangeLazy;
|
||||
|
||||
auto outapp = new EQApplicationPacket(packet_op, 0);
|
||||
QueuePacket(outapp);
|
||||
safe_delete(outapp);
|
||||
}
|
||||
|
||||
void Client::GrantNameChange() {
|
||||
|
||||
auto k = GetScopedBucketKeys();
|
||||
k.key = "name_change_allowed";
|
||||
k.value = "allowed"; // potentially put a timestamp here
|
||||
DataBucket::SetData(k);
|
||||
|
||||
InvokeChangeNameWindow(true);
|
||||
}
|
||||
|
||||
bool Client::IsPetNameChangeAllowed() {
|
||||
if (RuleB(Pets, AlwaysAllowPetRename)) {
|
||||
return true;
|
||||
|
||||
+8
-1
@@ -332,6 +332,10 @@ public:
|
||||
bool KeyRingClear();
|
||||
bool KeyRingRemove(uint32 item_id);
|
||||
void KeyRingList();
|
||||
bool IsNameChangeAllowed();
|
||||
void InvokeChangeNameWindow(bool immediate = true);
|
||||
bool ClearNameChange();
|
||||
void GrantNameChange();
|
||||
bool IsPetNameChangeAllowed();
|
||||
void GrantPetNameChange();
|
||||
void ClearPetNameChange();
|
||||
@@ -443,6 +447,8 @@ public:
|
||||
int64 ValidateBuyLineCost(std::map<uint32, BuylineItemDetails_Struct>& item_map);
|
||||
bool DoBarterBuyerChecks(BuyerLineSellItem_Struct& sell_line);
|
||||
bool DoBarterSellerChecks(BuyerLineSellItem_Struct& sell_line);
|
||||
void CancelBuyerTradeWindow();
|
||||
void CancelTraderTradeWindow();
|
||||
|
||||
void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho);
|
||||
bool ShouldISpawnFor(Client *c) { return !GMHideMe(c) && !IsHoveringForRespawn(); }
|
||||
@@ -509,7 +515,8 @@ public:
|
||||
bool AutoAttackEnabled() const { return auto_attack; }
|
||||
bool AutoFireEnabled() const { return auto_fire; }
|
||||
|
||||
bool ChangeFirstName(const char* in_firstname,const char* gmname);
|
||||
bool ChangeFirstName(const std::string in_firstname,const std::string gmname);
|
||||
bool ChangeFirstName(const std::string in_firstname);
|
||||
|
||||
void Duck();
|
||||
void Stand();
|
||||
|
||||
@@ -128,6 +128,7 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
|
||||
evolve_amount = exp * RuleR(EvolvingItems, PercentOfSoloExperience) / 100;
|
||||
}
|
||||
|
||||
inst->SetEvolveAddToCurrentAmount(evolve_amount);
|
||||
inst->CalculateEvolveProgression();
|
||||
|
||||
auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression(
|
||||
|
||||
+36
-12
@@ -826,6 +826,10 @@ void Client::CompleteConnect()
|
||||
if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) {
|
||||
InvokeChangePetName(false);
|
||||
}
|
||||
|
||||
if (IsNameChangeAllowed() && !RuleB(Character, AlwaysAllowNameChange)) {
|
||||
InvokeChangeNameWindow(false);
|
||||
}
|
||||
}
|
||||
|
||||
if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) {
|
||||
@@ -4256,7 +4260,7 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app)
|
||||
else {
|
||||
OnDisconnect(true);
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4548,14 +4552,14 @@ void Client::Handle_OP_ChangePetName(const EQApplicationPacket *app) {
|
||||
|
||||
auto p = (ChangePetName_Struct *) app->pBuffer;
|
||||
if (!IsPetNameChangeAllowed()) {
|
||||
p->response_code = ChangePetNameResponse::NotEligible;
|
||||
p->response_code = ChangeNameResponse::Ineligible;
|
||||
QueuePacket(app);
|
||||
return;
|
||||
}
|
||||
|
||||
p->response_code = ChangePetNameResponse::Denied;
|
||||
p->response_code = ChangeNameResponse::Denied;
|
||||
if (ChangePetName(p->new_pet_name)) {
|
||||
p->response_code = ChangePetNameResponse::Accepted;
|
||||
p->response_code = ChangeNameResponse::Accepted;
|
||||
}
|
||||
|
||||
QueuePacket(app);
|
||||
@@ -6776,6 +6780,21 @@ void Client::Handle_OP_GMLastName(const EQApplicationPacket *app)
|
||||
|
||||
void Client::Handle_OP_GMNameChange(const EQApplicationPacket *app)
|
||||
{
|
||||
if (app->size == sizeof(AltChangeName_Struct)) {
|
||||
auto p = (AltChangeName_Struct *) app->pBuffer;
|
||||
|
||||
if (!IsNameChangeAllowed()) {
|
||||
p->response_code = ChangeNameResponse::Ineligible;
|
||||
QueuePacket(app);
|
||||
return;
|
||||
}
|
||||
|
||||
p->response_code = ChangeFirstName(p->new_name) ? ChangeNameResponse::Accepted : ChangeNameResponse::Denied;
|
||||
QueuePacket(app);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (app->size != sizeof(GMName_Struct)) {
|
||||
LogError("Wrong size: OP_GMNameChange, size=[{}], expected [{}]", app->size, sizeof(GMName_Struct));
|
||||
return;
|
||||
@@ -7653,7 +7672,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
|
||||
log.char_id = CharacterID();
|
||||
log.guild_id = GuildID();
|
||||
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()) {
|
||||
auto augs = inst->GetAugmentIDs();
|
||||
log.aug_slot_one = augs.at(0);
|
||||
@@ -7737,7 +7760,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
|
||||
item.guild_id = GuildID();
|
||||
item.area = GuildBankDepositArea;
|
||||
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.permissions = GuildBankBankerOnly;
|
||||
if (cursor_item_inst->IsAugmented()) {
|
||||
@@ -7821,14 +7848,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
|
||||
break;
|
||||
}
|
||||
|
||||
if (inst->GetCharges() > 0) {
|
||||
gbwis->Quantity = 1;
|
||||
if (inst->GetCharges() > 0 || inst->IsStackable() || inst->GetItem()->MaxCharges > 0) {
|
||||
gbwis->Quantity = inst->GetCharges();
|
||||
}
|
||||
|
||||
if (inst->GetCharges() < 0) {
|
||||
gbwis->Quantity = 1;
|
||||
}
|
||||
|
||||
PushItemOnCursor(*inst.get());
|
||||
SendItemPacket(EQ::invslot::slotCursor, inst.get(), ItemPacketLimbo);
|
||||
GuildBanks->DeleteItem(GuildID(), gbwis->Area, gbwis->SlotID, gbwis->Quantity, this);
|
||||
@@ -15458,7 +15482,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
|
||||
);
|
||||
Message(
|
||||
Chat::Yellow,
|
||||
"Direct inventory delivey is not yet implemented. Please visit the vendor directly or purchase via parcel delivery."
|
||||
"Direct inventory delivery is not yet implemented. Please visit the vendor directly or purchase via parcel delivery."
|
||||
);
|
||||
in->method = BazaarByDirectToInventory;
|
||||
in->sub_action = Failed;
|
||||
|
||||
+44
-1
@@ -25,6 +25,7 @@
|
||||
#include "worldserver.h"
|
||||
#include "../common/repositories/character_expedition_lockouts_repository.h"
|
||||
#include "../common/repositories/dynamic_zone_lockouts_repository.h"
|
||||
#include <cereal/types/utility.hpp>
|
||||
|
||||
extern WorldServer worldserver;
|
||||
|
||||
@@ -162,14 +163,28 @@ void DynamicZone::CacheAllFromDatabase()
|
||||
}
|
||||
}
|
||||
|
||||
dz->UpdateMembers();
|
||||
zone->dynamic_zone_cache.emplace(dz_id, std::move(dz));
|
||||
}
|
||||
|
||||
if (!zone->dynamic_zone_cache.empty())
|
||||
{
|
||||
RequestMemberStatuses();
|
||||
}
|
||||
|
||||
LogInfo("Loaded [{}] dynamic zone(s)", Strings::Commify(zone->dynamic_zone_cache.size()));
|
||||
LogDynamicZones("Caching [{}] dynamic zone(s) took [{}s]", zone->dynamic_zone_cache.size(), bench.elapsed());
|
||||
}
|
||||
|
||||
void DynamicZone::RequestMemberStatuses()
|
||||
{
|
||||
ServerPacket pack(ServerOP_DzGetBulkMemberStatuses, sizeof(ServerDzCerealData_Struct));
|
||||
auto buf = reinterpret_cast<ServerDzCerealData_Struct*>(pack.pBuffer);
|
||||
buf->zone_id = static_cast<uint16_t>(zone->GetZoneID());
|
||||
buf->inst_id = static_cast<uint16_t>(zone->GetInstanceID());
|
||||
|
||||
worldserver.SendPacket(&pack);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
DynamicZone* FindDynamicZone(T pred)
|
||||
{
|
||||
@@ -849,6 +864,34 @@ void DynamicZone::HandleWorldMessage(ServerPacket* pack)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ServerOP_DzGetBulkMemberStatuses:
|
||||
{
|
||||
if (zone)
|
||||
{
|
||||
std::vector<std::pair<uint32_t, std::vector<DynamicZoneMember>>> dzs;
|
||||
dzs.reserve(zone->dynamic_zone_cache.size());
|
||||
|
||||
auto buf = reinterpret_cast<ServerDzCerealData_Struct*>(pack->pBuffer);
|
||||
EQ::Util::MemoryStreamReader ss(buf->cereal_data, buf->cereal_size);
|
||||
{
|
||||
cereal::BinaryInputArchive archive(ss);
|
||||
archive(dzs);
|
||||
}
|
||||
|
||||
for (const auto& [dz_id, members] : dzs)
|
||||
{
|
||||
if (auto dz = DynamicZone::FindDynamicZoneByID(dz_id))
|
||||
{
|
||||
for (const auto& member : members)
|
||||
{
|
||||
dz->SetInternalMemberStatus(member.id, member.status);
|
||||
}
|
||||
dz->m_has_member_statuses = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ServerOP_DzUpdateMemberStatus:
|
||||
{
|
||||
auto buf = reinterpret_cast<ServerDzMemberStatus_Struct*>(pack->pBuffer);
|
||||
|
||||
+2
-1
@@ -92,13 +92,13 @@ public:
|
||||
void SendMemberNameToZoneMembers(const std::string& char_name, bool remove);
|
||||
void SendMemberStatusToZoneMembers(const DynamicZoneMember& member);
|
||||
void SetLocked(bool lock, bool update_db = false, DzLockMsg lock_msg = DzLockMsg::None, uint32_t color = Chat::Yellow);
|
||||
void UpdateMembers();
|
||||
|
||||
std::string GetLootEvent(uint32_t id, DzLootEvent::Type type) const;
|
||||
void SetLootEvent(uint32_t id, const std::string& event, DzLootEvent::Type type);
|
||||
|
||||
private:
|
||||
static void StartAllClientRemovalTimers();
|
||||
static void RequestMemberStatuses();
|
||||
|
||||
uint16_t GetCurrentInstanceID() const override;
|
||||
uint16_t GetCurrentZoneID() const override;
|
||||
@@ -125,6 +125,7 @@ private:
|
||||
void SendWorldPlayerInvite(const std::string& inviter, const std::string& swap_name, const std::string& add_name, bool pending = false);
|
||||
void SetUpdatedDuration(uint32_t seconds);
|
||||
void TryAddClient(Client* add_client, const std::string& inviter, const std::string& swap_name, Client* leader = nullptr);
|
||||
void UpdateMembers();
|
||||
|
||||
std::unique_ptr<EQApplicationPacket> CreateExpireWarningPacket(uint32_t minutes_remaining);
|
||||
std::unique_ptr<EQApplicationPacket> CreateInfoPacket(bool clear = false);
|
||||
|
||||
@@ -14,7 +14,7 @@ void SetName(Client *c, const Seperator *sep)
|
||||
std::string new_name = sep->arg[2];
|
||||
std::string old_name = t->GetCleanName();
|
||||
|
||||
if (t->ChangeFirstName(new_name.c_str(), c->GetCleanName())) {
|
||||
if (t->ChangeFirstName(new_name, c->GetCleanName())) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
@@ -24,17 +24,13 @@ void SetName(Client *c, const Seperator *sep)
|
||||
).c_str()
|
||||
);
|
||||
|
||||
c->Message(Chat::White, "Sending player to char select.");
|
||||
|
||||
t->Kick("Name was changed");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Unable to rename {}. Check that the new name '{}' isn't already taken.",
|
||||
"Unable to rename {}. Check that the new name '{}' isn't already taken (Including Pet Names), or isn't invalid",
|
||||
old_name,
|
||||
new_name
|
||||
).c_str()
|
||||
|
||||
+6
-1
@@ -23,6 +23,11 @@ void NPC::AddLootTable(uint32 loottable_id, bool is_global)
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_resumed_from_zone_suspend) {
|
||||
LogZoneState("NPC [{}] is resuming from zone suspend, skipping", GetCleanName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_global) {
|
||||
m_loot_copper = 0;
|
||||
m_loot_silver = 0;
|
||||
@@ -277,7 +282,7 @@ void NPC::AddLootDrop(
|
||||
)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -3496,6 +3496,24 @@ std::string Lua_Client::GetAccountBucketRemaining(std::string bucket_name)
|
||||
return self->GetAccountBucketRemaining(bucket_name);
|
||||
}
|
||||
|
||||
void Lua_Client::GrantNameChange()
|
||||
{
|
||||
Lua_Safe_Call_Void();
|
||||
self->GrantNameChange();
|
||||
}
|
||||
|
||||
bool Lua_Client::IsNameChangeAllowed()
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->IsNameChangeAllowed();
|
||||
}
|
||||
|
||||
bool Lua_Client::ClearNameChange()
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->ClearNameChange();
|
||||
}
|
||||
|
||||
std::string Lua_Client::GetBandolierName(uint8 bandolier_slot)
|
||||
{
|
||||
Lua_Safe_Call_String();
|
||||
@@ -3635,6 +3653,7 @@ luabind::scope lua_register_client() {
|
||||
.def("CashReward", &Lua_Client::CashReward)
|
||||
.def("ChangeLastName", (void(Lua_Client::*)(std::string))&Lua_Client::ChangeLastName)
|
||||
.def("GrantPetNameChange", &Lua_Client::GrantPetNameChange)
|
||||
.def("ClearNameChange", &Lua_Client::ClearNameChange)
|
||||
.def("CharacterID", (uint32(Lua_Client::*)(void))&Lua_Client::CharacterID)
|
||||
.def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob))&Lua_Client::CheckIncreaseSkill)
|
||||
.def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob,int))&Lua_Client::CheckIncreaseSkill)
|
||||
@@ -3851,6 +3870,7 @@ luabind::scope lua_register_client() {
|
||||
.def("GrantAllAAPoints", (void(Lua_Client::*)(uint8,bool))&Lua_Client::GrantAllAAPoints)
|
||||
.def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int))&Lua_Client::GrantAlternateAdvancementAbility)
|
||||
.def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int, bool))&Lua_Client::GrantAlternateAdvancementAbility)
|
||||
.def("GrantNameChange", &Lua_Client::GrantNameChange)
|
||||
.def("GuildID", (uint32(Lua_Client::*)(void))&Lua_Client::GuildID)
|
||||
.def("GuildRank", (int(Lua_Client::*)(void))&Lua_Client::GuildRank)
|
||||
.def("HasAugmentEquippedByID", (bool(Lua_Client::*)(uint32))&Lua_Client::HasAugmentEquippedByID)
|
||||
@@ -3881,6 +3901,7 @@ luabind::scope lua_register_client() {
|
||||
.def("IsInAGuild", (bool(Lua_Client::*)(void))&Lua_Client::IsInAGuild)
|
||||
.def("IsLD", (bool(Lua_Client::*)(void))&Lua_Client::IsLD)
|
||||
.def("IsMedding", (bool(Lua_Client::*)(void))&Lua_Client::IsMedding)
|
||||
.def("IsNameChangeAllowed", &Lua_Client::IsNameChangeAllowed)
|
||||
.def("IsRaidGrouped", (bool(Lua_Client::*)(void))&Lua_Client::IsRaidGrouped)
|
||||
.def("IsSitting", (bool(Lua_Client::*)(void))&Lua_Client::IsSitting)
|
||||
.def("IsStanding", (bool(Lua_Client::*)(void))&Lua_Client::IsStanding)
|
||||
|
||||
@@ -609,6 +609,10 @@ public:
|
||||
void ShowZoneShardMenu();
|
||||
void GrantPetNameChange();
|
||||
|
||||
void GrantNameChange();
|
||||
bool IsNameChangeAllowed();
|
||||
bool ClearNameChange();
|
||||
|
||||
Lua_Expedition CreateExpedition(luabind::object expedition_info);
|
||||
Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players);
|
||||
Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool disable_messages);
|
||||
|
||||
+53
-1
@@ -727,6 +727,52 @@ std::string Lua_Zone::GetBucketRemaining(const std::string& bucket_name)
|
||||
return self->GetBucketRemaining(bucket_name);
|
||||
}
|
||||
|
||||
void Lua_Zone::ClearVariables()
|
||||
{
|
||||
Lua_Safe_Call_Void();
|
||||
self->ClearVariables();
|
||||
}
|
||||
|
||||
bool Lua_Zone::DeleteVariable(const std::string& variable_name)
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->DeleteVariable(variable_name);
|
||||
}
|
||||
|
||||
std::string Lua_Zone::GetVariable(const std::string& variable_name)
|
||||
{
|
||||
Lua_Safe_Call_String();
|
||||
return self->GetVariable(variable_name);
|
||||
}
|
||||
|
||||
luabind::object Lua_Zone::GetVariables(lua_State* L)
|
||||
{
|
||||
auto t = luabind::newtable(L);
|
||||
if (d_) {
|
||||
auto self = reinterpret_cast<NativeType*>(d_);
|
||||
auto l = self->GetVariables();
|
||||
int i = 1;
|
||||
for (const auto& v : l) {
|
||||
t[i] = v;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
void Lua_Zone::SetVariable(const std::string& variable_name, const std::string& variable_value)
|
||||
{
|
||||
Lua_Safe_Call_Void();
|
||||
self->SetVariable(variable_name, variable_value);
|
||||
}
|
||||
|
||||
bool Lua_Zone::VariableExists(const std::string& variable_name)
|
||||
{
|
||||
Lua_Safe_Call_Bool();
|
||||
return self->VariableExists(variable_name);
|
||||
}
|
||||
|
||||
luabind::scope lua_register_zone() {
|
||||
return luabind::class_<Lua_Zone>("Zones")
|
||||
.def(luabind::constructor<>())
|
||||
@@ -737,7 +783,9 @@ luabind::scope lua_register_zone() {
|
||||
.def("CanDoCombat", &Lua_Zone::CanDoCombat)
|
||||
.def("CanLevitate", &Lua_Zone::CanLevitate)
|
||||
.def("ClearSpawnTimers", &Lua_Zone::ClearSpawnTimers)
|
||||
.def("ClearVariables", &Lua_Zone::ClearVariables)
|
||||
.def("DeleteBucket", (void(Lua_Zone::*)(const std::string&))&Lua_Zone::DeleteBucket)
|
||||
.def("DeleteVariable", &Lua_Zone::DeleteVariable)
|
||||
.def("Depop", (void(Lua_Zone::*)(void))&Lua_Zone::Depop)
|
||||
.def("Depop", (void(Lua_Zone::*)(bool))&Lua_Zone::Depop)
|
||||
.def("Despawn", &Lua_Zone::Despawn)
|
||||
@@ -819,6 +867,8 @@ luabind::scope lua_register_zone() {
|
||||
.def("GetZoneType", &Lua_Zone::GetZoneType)
|
||||
.def("GetUnderworld", &Lua_Zone::GetUnderworld)
|
||||
.def("GetUnderworldTeleportIndex", &Lua_Zone::GetUnderworldTeleportIndex)
|
||||
.def("GetVariable", &Lua_Zone::GetVariable)
|
||||
.def("GetVariables", &Lua_Zone::GetVariables)
|
||||
.def("GetWalkSpeed", &Lua_Zone::GetWalkSpeed)
|
||||
.def("GetZoneZType", &Lua_Zone::GetZoneZType)
|
||||
.def("GetZoneTotalBlockedSpells", &Lua_Zone::GetZoneTotalBlockedSpells)
|
||||
@@ -849,6 +899,8 @@ luabind::scope lua_register_zone() {
|
||||
.def("SetInstanceTimer", &Lua_Zone::SetInstanceTimer)
|
||||
.def("SetInstanceTimeRemaining", &Lua_Zone::SetInstanceTimeRemaining)
|
||||
.def("SetIsHotzone", &Lua_Zone::SetIsHotzone)
|
||||
.def("ShowZoneGlobalLoot", &Lua_Zone::ShowZoneGlobalLoot);
|
||||
.def("SetVariable", &Lua_Zone::SetVariable)
|
||||
.def("ShowZoneGlobalLoot", &Lua_Zone::ShowZoneGlobalLoot)
|
||||
.def("VariableExists", &Lua_Zone::VariableExists);
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,12 @@ public:
|
||||
void SetInstanceTimeRemaining(uint32 time_remaining);
|
||||
void SetIsHotzone(bool is_hotzone);
|
||||
void ShowZoneGlobalLoot(Lua_Client c);
|
||||
void ClearVariables();
|
||||
bool DeleteVariable(const std::string& variable_name);
|
||||
std::string GetVariable(const std::string& variable_name);
|
||||
luabind::object GetVariables(lua_State* L);
|
||||
void SetVariable(const std::string& variable_name, const std::string& variable_value);
|
||||
bool VariableExists(const std::string& variable_name);
|
||||
|
||||
// data buckets
|
||||
void SetBucket(const std::string& bucket_name, const std::string& bucket_value);
|
||||
|
||||
+4
-1
@@ -131,7 +131,8 @@ Mob::Mob(
|
||||
m_scan_close_mobs_timer(6000),
|
||||
m_see_close_mobs_timer(1000),
|
||||
m_mob_check_moving_timer(1000),
|
||||
bot_attack_flag_timer(10000)
|
||||
bot_attack_flag_timer(10000),
|
||||
m_destroying(false)
|
||||
{
|
||||
mMovementManager = &MobMovementManager::Get();
|
||||
mMovementManager->AddMob(this);
|
||||
@@ -531,6 +532,8 @@ Mob::Mob(
|
||||
|
||||
Mob::~Mob()
|
||||
{
|
||||
m_destroying = true;
|
||||
|
||||
entity_list.RemoveMobFromCloseLists(this);
|
||||
m_close_mobs.clear();
|
||||
|
||||
|
||||
@@ -1510,6 +1510,7 @@ public:
|
||||
|
||||
void ClearDataBucketCache();
|
||||
bool IsGuildmaster() const;
|
||||
bool IsDestroying() const { return m_destroying; }
|
||||
|
||||
protected:
|
||||
void CommonDamage(Mob* other, int64 &damage, const uint16 spell_id, const EQ::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks specal = eSpecialAttacks::None);
|
||||
@@ -1932,6 +1933,7 @@ private:
|
||||
EQ::InventoryProfile m_inv;
|
||||
std::shared_ptr<HealRotation> m_target_of_heal_rotation;
|
||||
bool m_manual_follow;
|
||||
bool m_destroying;
|
||||
|
||||
void SetHeroicStrBonuses(StatBonuses* n);
|
||||
void SetHeroicStaBonuses(StatBonuses* n);
|
||||
|
||||
@@ -626,6 +626,9 @@ inline void NPCCommandsMenu(Client* client, NPC* npc)
|
||||
|
||||
if (npc->GetLoottableID() > 0) {
|
||||
menu_commands += "[" + Saylink::Silent("#npcloot show", "Loot") + "] ";
|
||||
if (npc) {
|
||||
menu_commands += fmt::format(" Item(s) ({}) ", npc->GetLootItems().size());
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
m_corpse_queue_timer(1000),
|
||||
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),
|
||||
monkattack_timer(1000),
|
||||
knightattack_timer(1000),
|
||||
|
||||
@@ -3261,6 +3261,21 @@ std::string Perl_Client_GetAccountBucketRemaining(Client* self, std::string buck
|
||||
return self->GetAccountBucketRemaining(bucket_name);
|
||||
}
|
||||
|
||||
void Perl_Client_GrantNameChange(Client* self)
|
||||
{
|
||||
self->GrantNameChange();
|
||||
}
|
||||
|
||||
bool Perl_Client_IsNameChangeAllowed(Client* self)
|
||||
{
|
||||
return self->IsNameChangeAllowed();
|
||||
}
|
||||
|
||||
bool Perl_Client_ClearNameChange(Client* self)
|
||||
{
|
||||
return self->ClearNameChange();
|
||||
}
|
||||
|
||||
std::string Perl_Client_GetBandolierName(Client* self, uint8 bandolier_slot)
|
||||
{
|
||||
return self->GetBandolierName(bandolier_slot);
|
||||
@@ -3393,6 +3408,7 @@ void perl_register_client()
|
||||
package.add("CashReward", &Perl_Client_CashReward);
|
||||
package.add("ChangeLastName", &Perl_Client_ChangeLastName);
|
||||
package.add("GrantPetNameChange", &Perl_Client_GrantPetNameChange);
|
||||
package.add("ClearNameChange", (bool(*)(Client*))&Perl_Client_ClearNameChange);
|
||||
package.add("CharacterID", &Perl_Client_CharacterID);
|
||||
package.add("CheckIncreaseSkill", (bool(*)(Client*, int))&Perl_Client_CheckIncreaseSkill);
|
||||
package.add("CheckIncreaseSkill", (bool(*)(Client*, int, int))&Perl_Client_CheckIncreaseSkill);
|
||||
@@ -3607,6 +3623,7 @@ void perl_register_client()
|
||||
package.add("GrantAllAAPoints", (void(*)(Client*, uint8, bool))&Perl_Client_GrantAllAAPoints);
|
||||
package.add("GrantAlternateAdvancementAbility", (bool(*)(Client*, int, int))&Perl_Client_GrantAlternateAdvancementAbility);
|
||||
package.add("GrantAlternateAdvancementAbility", (bool(*)(Client*, int, int, bool))&Perl_Client_GrantAlternateAdvancementAbility);
|
||||
package.add("GrantNameChange", (void(*)(Client*))&Perl_Client_GrantNameChange);
|
||||
package.add("GuildID", &Perl_Client_GuildID);
|
||||
package.add("GuildRank", &Perl_Client_GuildRank);
|
||||
package.add("HasAugmentEquippedByID", &Perl_Client_HasAugmentEquippedByID);
|
||||
@@ -3637,6 +3654,7 @@ void perl_register_client()
|
||||
package.add("IsInAGuild", &Perl_Client_IsInAGuild);
|
||||
package.add("IsLD", &Perl_Client_IsLD);
|
||||
package.add("IsMedding", &Perl_Client_IsMedding);
|
||||
package.add("IsNameChangeAllowed", (bool(*)(Client*))&Perl_Client_IsNameChangeAllowed);
|
||||
package.add("IsRaidGrouped", &Perl_Client_IsRaidGrouped);
|
||||
package.add("IsSitting", &Perl_Client_IsSitting);
|
||||
package.add("IsStanding", &Perl_Client_IsStanding);
|
||||
|
||||
@@ -561,6 +561,43 @@ std::string Perl_Zone_GetBucketRemaining(Zone* self, const std::string bucket_na
|
||||
return self->GetBucketRemaining(bucket_name);
|
||||
}
|
||||
|
||||
void Perl_Zone_ClearVariables(Zone* self)
|
||||
{
|
||||
self->ClearVariables();
|
||||
}
|
||||
|
||||
bool Perl_Zone_DeleteVariable(Zone* self, const std::string variable_name)
|
||||
{
|
||||
return self->DeleteVariable(variable_name);
|
||||
}
|
||||
|
||||
std::string Perl_Zone_GetVariable(Zone* self, const std::string variable_name)
|
||||
{
|
||||
return self->GetVariable(variable_name);
|
||||
}
|
||||
|
||||
perl::array Perl_Zone_GetVariables(Zone* self)
|
||||
{
|
||||
perl::array a;
|
||||
|
||||
const auto& l = self->GetVariables();
|
||||
for (const auto& v : l) {
|
||||
a.push_back(v);
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
void Perl_Zone_SetVariable(Zone* self, const std::string variable_name, const std::string variable_value)
|
||||
{
|
||||
self->SetVariable(variable_name, variable_value);
|
||||
}
|
||||
|
||||
bool Perl_Zone_VariableExists(Zone* self, const std::string variable_name)
|
||||
{
|
||||
return self->VariableExists(variable_name);
|
||||
}
|
||||
|
||||
void perl_register_zone()
|
||||
{
|
||||
perl::interpreter perl(PERL_GET_THX);
|
||||
@@ -573,7 +610,9 @@ void perl_register_zone()
|
||||
package.add("CanDoCombat", &Perl_Zone_CanDoCombat);
|
||||
package.add("CanLevitate", &Perl_Zone_CanLevitate);
|
||||
package.add("ClearSpawnTimers", &Perl_Zone_ClearSpawnTimers);
|
||||
package.add("ClearVariables", &Perl_Zone_ClearVariables);
|
||||
package.add("DeleteBucket", &Perl_Zone_DeleteBucket);
|
||||
package.add("DeleteVariable", &Perl_Zone_DeleteVariable);
|
||||
package.add("Depop", (void(*)(Zone*))&Perl_Zone_Depop);
|
||||
package.add("Depop", (void(*)(Zone*, bool))&Perl_Zone_Depop);
|
||||
package.add("Despawn", &Perl_Zone_Despawn);
|
||||
@@ -655,6 +694,8 @@ void perl_register_zone()
|
||||
package.add("GetZoneType", &Perl_Zone_GetZoneType);
|
||||
package.add("GetUnderworld", &Perl_Zone_GetUnderworld);
|
||||
package.add("GetUnderworldTeleportIndex", &Perl_Zone_GetUnderworldTeleportIndex);
|
||||
package.add("GetVariable", &Perl_Zone_GetVariable);
|
||||
package.add("GetVariables", &Perl_Zone_GetVariables);
|
||||
package.add("GetWalkSpeed", &Perl_Zone_GetWalkSpeed);
|
||||
package.add("GetZoneZType", &Perl_Zone_GetZoneZType);
|
||||
package.add("GetZoneTotalBlockedSpells", &Perl_Zone_GetZoneTotalBlockedSpells);
|
||||
@@ -685,7 +726,9 @@ void perl_register_zone()
|
||||
package.add("SetInstanceTimer", &Perl_Zone_SetInstanceTimer);
|
||||
package.add("SetInstanceTimeRemaining", &Perl_Zone_SetInstanceTimeRemaining);
|
||||
package.add("SetIsHotzone", &Perl_Zone_SetIsHotzone);
|
||||
package.add("SetVariable", &Perl_Zone_SetVariable);
|
||||
package.add("ShowZoneGlobalLoot", &Perl_Zone_ShowZoneGlobalLoot);
|
||||
package.add("VariableExists", &Perl_Zone_VariableExists);
|
||||
}
|
||||
|
||||
#endif //EMBPERL_XS_CLASSES
|
||||
|
||||
@@ -435,6 +435,10 @@ int QuestParserCollection::EventNPC(
|
||||
std::vector<std::any>* extra_pointers
|
||||
)
|
||||
{
|
||||
if (npc->IsResumedFromZoneSuspend() && npc->IsQueuedForCorpse()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int local_return = EventNPCLocal(event_id, npc, init, data, extra_data, extra_pointers);
|
||||
const int global_return = EventNPCGlobal(event_id, npc, init, data, extra_data, extra_pointers);
|
||||
const int default_return = DispatchEventNPC(event_id, npc, init, data, extra_data, extra_pointers);
|
||||
@@ -935,6 +939,12 @@ QuestInterface* QuestParserCollection::GetQIByNPCQuest(uint32 npc_id, std::strin
|
||||
|
||||
Strings::FindReplace(npc_name, "`", "-");
|
||||
|
||||
const std::string& npc_id_and_name = fmt::format(
|
||||
"{}_{}",
|
||||
npc_name,
|
||||
npc_id
|
||||
);
|
||||
|
||||
const std::string& global_path = fmt::format(
|
||||
"{}/{}",
|
||||
path.GetQuestsPath(),
|
||||
@@ -955,13 +965,16 @@ QuestInterface* QuestParserCollection::GetQIByNPCQuest(uint32 npc_id, std::strin
|
||||
);
|
||||
|
||||
std::vector<std::string> file_names = {
|
||||
fmt::format("{}/{}", zone_versioned_path, npc_id), // Local versioned by NPC ID ./quests/zone/v0/10.ext
|
||||
fmt::format("{}/{}", zone_versioned_path, npc_name), // Local versioned by NPC Name ./quests/zone/v0/npc.ext
|
||||
fmt::format("{}/{}", zone_versioned_path, npc_id), // Local versioned by NPC ID (./quests/zone/v0/10.ext)
|
||||
fmt::format("{}/{}", zone_versioned_path, npc_name), // Local versioned by NPC Name (./quests/zone/v0/name.ext)
|
||||
fmt::format("{}/{}", zone_versioned_path, npc_id_and_name), // Local versioned by NPC ID and NPC Name (./quests/zone/v0/10_name.ext)
|
||||
fmt::format("{}/{}", zone_path, npc_id), // Local by NPC ID
|
||||
fmt::format("{}/{}", zone_path, npc_name), // Local by NPC Name
|
||||
fmt::format("{}/{}", zone_path, npc_id_and_name), // Local by NPC ID and NPC Name
|
||||
fmt::format("{}/{}", global_path, npc_id), // Global by NPC ID
|
||||
fmt::format("{}/{}", global_path, npc_name), // Global by NPC ID
|
||||
fmt::format("{}/default", zone_versioned_path), // Zone Default ./quests/zone/v0/default.ext
|
||||
fmt::format("{}/{}", global_path, npc_id_and_name), // Global by NPC ID and NPC Name
|
||||
fmt::format("{}/default", zone_versioned_path), // Zone Versioned Default (./quests/zone/v0/default.ext)
|
||||
fmt::format("{}/default", zone_path), // Zone Default
|
||||
fmt::format("{}/default", global_path), // Global Default
|
||||
};
|
||||
|
||||
+20
-3
@@ -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) {
|
||||
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;
|
||||
if (t = content_db.LoadNPCTypesData(npc_id)) {
|
||||
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) {
|
||||
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);
|
||||
if(other != nullptr) {
|
||||
return other;
|
||||
@@ -1292,15 +1310,14 @@ void QuestManager::rename(std::string name) {
|
||||
QuestManagerCurrentQuestVars();
|
||||
if (initiator) {
|
||||
std::string current_name = initiator->GetName();
|
||||
if (initiator->ChangeFirstName(name.c_str(), current_name.c_str())) {
|
||||
if (initiator->ChangeFirstName(name)) {
|
||||
initiator->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Successfully renamed to {}, kicking to character select.",
|
||||
"Successfully renamed to {}.",
|
||||
name
|
||||
).c_str()
|
||||
);
|
||||
initiator->Kick("Name was changed.");
|
||||
} else {
|
||||
initiator->Message(
|
||||
Chat::Red,
|
||||
|
||||
@@ -277,6 +277,16 @@ bool Spawn2::Process() {
|
||||
|
||||
npcthis = npc;
|
||||
|
||||
if (!m_entity_variables.empty()) {
|
||||
for (auto &var : m_entity_variables) {
|
||||
npc->SetEntityVariable(var.first, var.second);
|
||||
}
|
||||
m_entity_variables = {};
|
||||
}
|
||||
|
||||
npc->SetResumedFromZoneSuspend(m_resumed_from_zone_suspend);
|
||||
m_resumed_from_zone_suspend = false;
|
||||
|
||||
npc->AddLootTable();
|
||||
if (npc->DropsGlobalLoot()) {
|
||||
npc->CheckGlobalLootTables();
|
||||
|
||||
@@ -75,6 +75,9 @@ public:
|
||||
int16 GetConditionMinValue() const { return condition_min_value; }
|
||||
int16 GetAnimation () { return anim; }
|
||||
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; }
|
||||
inline void SetEntityVariables(std::map<std::string, std::string> vars) { m_entity_variables = vars; }
|
||||
|
||||
protected:
|
||||
friend class Zone;
|
||||
@@ -101,6 +104,8 @@ private:
|
||||
EmuAppearance anim;
|
||||
bool IsDespawned;
|
||||
uint32 killcount;
|
||||
bool m_resumed_from_zone_suspend = false;
|
||||
std::map<std::string, std::string> m_entity_variables = {};
|
||||
};
|
||||
|
||||
class SpawnCondition {
|
||||
|
||||
@@ -1894,6 +1894,13 @@ void Client::SellToBuyer(const EQApplicationPacket *app)
|
||||
break;
|
||||
}
|
||||
|
||||
if (sell_line.purchase_method == BarterInBazaar && buyer->IsThereACustomer()) {
|
||||
auto customer = entity_list.GetClientByID(buyer->GetCustomerID());
|
||||
if (customer) {
|
||||
customer->CancelBuyerTradeWindow();
|
||||
}
|
||||
}
|
||||
|
||||
if (!DoBarterBuyerChecks(sell_line)) {
|
||||
return;
|
||||
};
|
||||
@@ -3825,3 +3832,18 @@ bool Client::DoBarterSellerChecks(BuyerLineSellItem_Struct &sell_line)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Client::CancelBuyerTradeWindow()
|
||||
{
|
||||
auto end_session = new EQApplicationPacket(OP_Barter, sizeof(BuyerRemoveItemFromMerchantWindow_Struct));
|
||||
auto data = reinterpret_cast<BuyerRemoveItemFromMerchantWindow_Struct *>(end_session->pBuffer);
|
||||
data->action = Barter_BuyerInspectBegin;
|
||||
|
||||
FastQueuePacket(&end_session);
|
||||
}
|
||||
|
||||
void Client::CancelTraderTradeWindow()
|
||||
{
|
||||
auto end_session = new EQApplicationPacket(OP_ShopEnd);
|
||||
FastQueuePacket(&end_session);
|
||||
}
|
||||
|
||||
@@ -3410,6 +3410,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
|
||||
case ServerOP_DzRemoveAllMembers:
|
||||
case ServerOP_DzDurationUpdate:
|
||||
case ServerOP_DzGetMemberStatuses:
|
||||
case ServerOP_DzGetBulkMemberStatuses:
|
||||
case ServerOP_DzSetCompass:
|
||||
case ServerOP_DzSetSafeReturn:
|
||||
case ServerOP_DzSetZoneIn:
|
||||
@@ -3785,6 +3786,13 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
|
||||
return;
|
||||
}
|
||||
|
||||
if (trader_pc->IsThereACustomer()) {
|
||||
auto customer = entity_list.GetClientByID(trader_pc->GetCustomerID());
|
||||
if (customer) {
|
||||
customer->CancelTraderTradeWindow();
|
||||
}
|
||||
}
|
||||
|
||||
auto item_sn = Strings::ToUnsignedBigInt(in->trader_buy_struct.serial_number);
|
||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Trader, sizeof(TraderBuy_Struct));
|
||||
auto data = (TraderBuy_Struct *) outapp->pBuffer;
|
||||
@@ -3980,6 +3988,12 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
|
||||
worldserver.SendPacket(pack);
|
||||
return;
|
||||
}
|
||||
if (buyer->IsThereACustomer()) {
|
||||
auto customer = entity_list.GetClientByID(buyer->GetCustomerID());
|
||||
if (customer) {
|
||||
customer->CancelBuyerTradeWindow();
|
||||
}
|
||||
}
|
||||
|
||||
BuyerLineSellItem_Struct sell_line{};
|
||||
sell_line.item_id = in->buy_item_id;
|
||||
|
||||
+71
-1
@@ -887,7 +887,10 @@ void Zone::Shutdown(bool quiet)
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -3218,5 +3221,72 @@ void Zone::DisableRespawnTimers()
|
||||
}
|
||||
}
|
||||
|
||||
void Zone::ClearVariables()
|
||||
{
|
||||
m_zone_variables.clear();
|
||||
}
|
||||
|
||||
bool Zone::DeleteVariable(const std::string& variable_name)
|
||||
{
|
||||
if (m_zone_variables.empty() || variable_name.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto v = m_zone_variables.find(variable_name);
|
||||
if (v == m_zone_variables.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_zone_variables.erase(v);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Zone::GetVariable(const std::string& variable_name)
|
||||
{
|
||||
if (m_zone_variables.empty() || variable_name.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
const auto& v = m_zone_variables.find(variable_name);
|
||||
|
||||
return v != m_zone_variables.end() ? v->second : std::string();
|
||||
}
|
||||
|
||||
std::vector<std::string> Zone::GetVariables()
|
||||
{
|
||||
std::vector<std::string> l;
|
||||
|
||||
if (m_zone_variables.empty()) {
|
||||
return l;
|
||||
}
|
||||
|
||||
l.reserve(m_zone_variables.size());
|
||||
|
||||
for (const auto& v : m_zone_variables) {
|
||||
l.emplace_back(v.first);
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
void Zone::SetVariable(const std::string& variable_name, const std::string& variable_value)
|
||||
{
|
||||
if (variable_name.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_zone_variables[variable_name] = variable_value;
|
||||
}
|
||||
|
||||
bool Zone::VariableExists(const std::string& variable_name)
|
||||
{
|
||||
if (m_zone_variables.empty() || variable_name.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_zone_variables.find(variable_name) != m_zone_variables.end();
|
||||
}
|
||||
|
||||
#include "zone_save_state.cpp"
|
||||
#include "zone_loot.cpp"
|
||||
|
||||
@@ -197,6 +197,13 @@ public:
|
||||
int32 MobsAggroCount() { return aggroedmobs; }
|
||||
DynamicZone *GetDynamicZone();
|
||||
|
||||
void ClearVariables();
|
||||
bool DeleteVariable(const std::string& variable_name);
|
||||
std::string GetVariable(const std::string& variable_name);
|
||||
std::vector<std::string> GetVariables();
|
||||
void SetVariable(const std::string& variable_name, const std::string& variable_value);
|
||||
bool VariableExists(const std::string& variable_name);
|
||||
|
||||
IPathfinder *pathing;
|
||||
std::vector<NPC_Emote_Struct *> npc_emote_list;
|
||||
LinkedList<Spawn2 *> spawn2_list;
|
||||
@@ -244,6 +251,8 @@ public:
|
||||
|
||||
std::vector<uint32> discovered_items;
|
||||
|
||||
std::map<std::string, std::string> m_zone_variables;
|
||||
|
||||
time_t weather_timer;
|
||||
Timer spawn2_timer;
|
||||
Timer hot_reload_timer;
|
||||
|
||||
+221
-62
@@ -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)
|
||||
{
|
||||
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)) {
|
||||
LogZoneState("Invalid JSON data for NPC [{}]", npc->GetNPCTypeID());
|
||||
return;
|
||||
@@ -66,6 +96,11 @@ inline void LoadLootStateData(Zone *zone, NPC *npc, const std::string &loot_data
|
||||
return;
|
||||
}
|
||||
|
||||
// reset
|
||||
npc->RemoveLootCash();
|
||||
npc->ClearLootItems();
|
||||
|
||||
// add loot
|
||||
npc->AddLootCash(l.copper, l.silver, l.gold, l.platinum);
|
||||
|
||||
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
|
||||
if (e.lootdrop_id == 0) {
|
||||
npc->AddItem(e.item_id, e.charges);
|
||||
npc->AddItem(e.item_id, e.charges, true);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -169,31 +204,44 @@ inline std::string GetLootSerialized(Corpse *c)
|
||||
return "";
|
||||
}
|
||||
|
||||
inline std::map<std::string, std::string> GetVariablesDeserialized(const std::string &entity_variables)
|
||||
{
|
||||
std::map<std::string, std::string> deserialized_map;
|
||||
|
||||
if (entity_variables.empty()) {
|
||||
return deserialized_map;
|
||||
}
|
||||
|
||||
if (!Strings::IsValidJson(entity_variables)) {
|
||||
LogZoneState("Invalid JSON data for entity variables");
|
||||
return deserialized_map;
|
||||
}
|
||||
|
||||
try {
|
||||
std::stringstream ss;
|
||||
{
|
||||
ss << entity_variables;
|
||||
cereal::JSONInputArchive ar(ss);
|
||||
ar(deserialized_map);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
LogZoneState("Failed to load entity variables [{}]", e.what());
|
||||
}
|
||||
|
||||
return deserialized_map;
|
||||
}
|
||||
|
||||
inline void LoadNPCEntityVariables(NPC *n, const std::string &entity_variables)
|
||||
{
|
||||
if (!RuleB(Zone, StateSaveEntityVariables)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Strings::IsValidJson(entity_variables)) {
|
||||
LogZoneState("Invalid JSON data for NPC [{}]", n->GetNPCTypeID());
|
||||
if (entity_variables.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> deserialized_map;
|
||||
try {
|
||||
std::istringstream is(entity_variables);
|
||||
{
|
||||
cereal::JSONInputArchive archive(is);
|
||||
archive(deserialized_map);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
LogZoneState("Failed to load entity variables for NPC [{}] [{}]", n->GetNPCTypeID(), e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &[key, value]: deserialized_map) {
|
||||
for (const auto &[key, value]: GetVariablesDeserialized(entity_variables)) {
|
||||
n->SetEntityVariable(key, value);
|
||||
}
|
||||
}
|
||||
@@ -204,6 +252,10 @@ inline void LoadNPCBuffs(NPC *n, const std::string &buffs)
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffs.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Strings::IsValidJson(buffs)) {
|
||||
LogZoneState("Invalid JSON data for NPC [{}]", n->GetNPCTypeID());
|
||||
return;
|
||||
@@ -236,6 +288,10 @@ inline std::vector<uint32_t> GetLootdropIds(const std::vector<ZoneStateSpawnsRep
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Strings::IsValidJson(s.loot_data)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LootStateData l{};
|
||||
try {
|
||||
std::stringstream ss;
|
||||
@@ -263,18 +319,30 @@ inline std::vector<uint32_t> GetLootdropIds(const std::vector<ZoneStateSpawnsRep
|
||||
return lootdrop_ids;
|
||||
}
|
||||
|
||||
inline void LoadNPCStatePreSpawn(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s)
|
||||
{
|
||||
LoadNPCEntityVariables(n, s.entity_variables);
|
||||
}
|
||||
|
||||
inline void LoadNPCState(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s)
|
||||
{
|
||||
n->SetHP(s.hp);
|
||||
n->SetMana(s.mana);
|
||||
n->SetEndurance(s.endurance);
|
||||
if (s.hp > 0) {
|
||||
n->SetHP(s.hp);
|
||||
}
|
||||
if (s.mana > 0) {
|
||||
n->SetMana(s.mana);
|
||||
}
|
||||
if (s.endurance > 0) {
|
||||
n->SetEndurance(s.endurance);
|
||||
}
|
||||
|
||||
if (s.grid) {
|
||||
n->AssignWaypoints(s.grid, s.current_waypoint);
|
||||
}
|
||||
|
||||
n->SetResumedFromZoneSuspend(false);
|
||||
LoadLootStateData(zone, n, s.loot_data);
|
||||
LoadNPCEntityVariables(n, s.entity_variables);
|
||||
n->SetResumedFromZoneSuspend(true);
|
||||
LoadNPCBuffs(n, s.buffs);
|
||||
|
||||
if (s.is_corpse) {
|
||||
@@ -287,10 +355,61 @@ inline void LoadNPCState(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStat
|
||||
n->Depop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
n->SetPosition(s.x, s.y, s.z);
|
||||
n->SetHeading(s.heading);
|
||||
n->SetResumedFromZoneSuspend(true);
|
||||
}
|
||||
|
||||
inline std::string GetZoneVariablesSerialized(Zone *z)
|
||||
{
|
||||
std::map<std::string, std::string> variables;
|
||||
|
||||
for (const auto &k: z->GetVariables()) {
|
||||
variables[k] = z->GetVariable(k);
|
||||
}
|
||||
|
||||
try {
|
||||
std::ostringstream os;
|
||||
{
|
||||
cereal::JSONOutputArchiveSingleLine archive(os);
|
||||
archive(variables);
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
LogZoneState("Failed to serialize variables for zone [{}]", e.what());
|
||||
return "";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
inline void LoadZoneVariables(Zone *z, const std::string &variables)
|
||||
{
|
||||
if (!Strings::IsValidJson(variables)) {
|
||||
LogZoneState("Invalid JSON data for zone [{}]", variables);
|
||||
return;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> deserialized_map;
|
||||
try {
|
||||
std::istringstream is(variables);
|
||||
{
|
||||
cereal::JSONInputArchive archive(is);
|
||||
archive(deserialized_map);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
LogZoneState("Failed to load zone variables [{}]", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &[key, value]: deserialized_map) {
|
||||
z->SetVariable(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
bool Zone::LoadZoneState(
|
||||
std::unordered_map<uint32, uint32> spawn_times,
|
||||
std::vector<Spawn2DisabledRepository::Spawn2Disabled> disabled_spawns
|
||||
@@ -299,7 +418,7 @@ bool Zone::LoadZoneState(
|
||||
auto spawn_states = ZoneStateSpawnsRepository::GetWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"zone_id = {} AND instance_id = {}",
|
||||
"zone_id = {} AND instance_id = {} ORDER BY is_zone DESC, spawn2_id ASC",
|
||||
zoneid,
|
||||
zone->GetInstanceID()
|
||||
)
|
||||
@@ -307,6 +426,16 @@ bool Zone::LoadZoneState(
|
||||
|
||||
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);
|
||||
zone->LoadLootDrops(lootdrop_ids);
|
||||
|
||||
@@ -315,11 +444,12 @@ bool Zone::LoadZoneState(
|
||||
zone->Process();
|
||||
|
||||
for (auto &s: spawn_states) {
|
||||
if (s.spawngroup_id == 0) {
|
||||
if (s.is_zone) {
|
||||
LoadZoneVariables(zone, s.entity_variables);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s.is_corpse) {
|
||||
if (s.spawngroup_id == 0 || s.is_corpse || s.is_zone) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -359,22 +489,21 @@ bool Zone::LoadZoneState(
|
||||
|
||||
if (spawn_time_left == 0) {
|
||||
new_spawn->SetCurrentNPCID(s.npc_id);
|
||||
new_spawn->SetResumedFromZoneSuspend(true);
|
||||
new_spawn->SetEntityVariables(GetVariablesDeserialized(s.entity_variables));
|
||||
}
|
||||
|
||||
spawn2_list.Insert(new_spawn);
|
||||
new_spawn->Process();
|
||||
auto n = new_spawn->GetNPC();
|
||||
if (n) {
|
||||
n->ClearLootItems();
|
||||
if (s.grid > 0) {
|
||||
n->AssignWaypoints(s.grid, s.current_waypoint);
|
||||
}
|
||||
LoadNPCState(zone, n, s);
|
||||
}
|
||||
}
|
||||
|
||||
// dynamic spawns, quest spawns, triggers etc.
|
||||
for (auto &s: spawn_states) {
|
||||
if (s.spawngroup_id > 0) {
|
||||
if (s.spawngroup_id > 0 || s.is_zone) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -391,6 +520,15 @@ bool Zone::LoadZoneState(
|
||||
GravityBehavior::Water
|
||||
);
|
||||
|
||||
npc->SetResumedFromZoneSuspend(true);
|
||||
|
||||
// tag as corpse before we add to entity list to prevent quest triggers
|
||||
if (s.is_corpse) {
|
||||
npc->SetQueuedToCorpse();
|
||||
}
|
||||
|
||||
LoadNPCStatePreSpawn(zone, npc, s);
|
||||
|
||||
entity_list.AddNPC(npc, true, true);
|
||||
|
||||
LoadNPCState(zone, npc, s);
|
||||
@@ -426,44 +564,43 @@ inline void SaveNPCState(NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s)
|
||||
variables[k] = n->GetEntityVariable(k);
|
||||
}
|
||||
|
||||
try {
|
||||
std::ostringstream os;
|
||||
{
|
||||
cereal::JSONOutputArchiveSingleLine archive(os);
|
||||
archive(variables);
|
||||
if (!variables.empty()) {
|
||||
try {
|
||||
std::ostringstream os;
|
||||
{
|
||||
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
|
||||
auto buffs = n->GetBuffs();
|
||||
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) {
|
||||
valid_buffs.push_back(buffs[index]);
|
||||
if (buffs) {
|
||||
std::vector<Buffs_Struct> valid_buffs;
|
||||
for (int index = 0; index < n->GetMaxBuffSlots(); index++) {
|
||||
if (buffs[index].spellid != 0 && buffs[index].spellid != 65535) {
|
||||
valid_buffs.push_back(buffs[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
std::ostringstream os = std::ostringstream();
|
||||
{
|
||||
cereal::JSONOutputArchiveSingleLine archive(os);
|
||||
archive(cereal::make_nvp("buffs", valid_buffs));
|
||||
if (!valid_buffs.empty()) {
|
||||
try {
|
||||
std::ostringstream os = std::ostringstream();
|
||||
{
|
||||
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
|
||||
@@ -518,13 +655,19 @@ void Zone::SaveZoneState()
|
||||
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()) {
|
||||
// everything below here is dynamically spawned
|
||||
bool ignore_npcs =
|
||||
n.second->GetSpawnGroupId() > 0 ||
|
||||
n.second->GetNPCTypeID() < 100 ||
|
||||
n.second->HasOwner();
|
||||
n.second->GetNPCTypeID() == 500 || // Trap::CreateHiddenTrigger
|
||||
n.second->IsAura() ||
|
||||
n.second->IsBot() ||
|
||||
n.second->IsMerc() ||
|
||||
n.second->IsTrap() ||
|
||||
n.second->GetSwarmOwner() ||
|
||||
n.second->IsPet();
|
||||
if (ignore_npcs) {
|
||||
continue;
|
||||
}
|
||||
@@ -560,6 +703,17 @@ void Zone::SaveZoneState()
|
||||
spawns.emplace_back(s);
|
||||
}
|
||||
|
||||
// zone state variables
|
||||
if (!GetVariables().empty()) {
|
||||
ZoneStateSpawnsRepository::ZoneStateSpawns z{};
|
||||
z.zone_id = GetZoneID();
|
||||
z.instance_id = GetInstanceID();
|
||||
z.is_zone = 1;
|
||||
z.entity_variables = GetZoneVariablesSerialized(this);
|
||||
|
||||
spawns.emplace_back(z);
|
||||
}
|
||||
|
||||
ZoneStateSpawnsRepository::DeleteWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
@@ -569,6 +723,11 @@ void Zone::SaveZoneState()
|
||||
)
|
||||
);
|
||||
|
||||
if (!IsZoneStateValid(spawns)) {
|
||||
LogInfo("No valid zone state data to save");
|
||||
return;
|
||||
}
|
||||
|
||||
ZoneStateSpawnsRepository::InsertMany(database, spawns);
|
||||
|
||||
LogInfo("Saved [{}] zone state spawns", Strings::Commify(spawns.size()));
|
||||
|
||||
Reference in New Issue
Block a user