mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-23 04:52:29 +00:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9be2485330 | |||
| a2bf10624a | |||
| ed58d16f1f | |||
| e9b45fb360 | |||
| 803972873a | |||
| d9e57eca79 | |||
| 6429dc80d3 | |||
| c4262b3fa6 | |||
| 92128b98fd | |||
| b9cfdea76c | |||
| c8a7066d0e | |||
| 950cc4a325 | |||
| 82b48fe6e8 | |||
| ca9c1fdd24 | |||
| fe08961d25 | |||
| 5b9f7ff4c9 | |||
| 23743a4050 | |||
| 444d688ad2 | |||
| d554eb3423 | |||
| deb298dda7 | |||
| 19e785b842 | |||
| 7b9691d486 | |||
| 30fddcc5a0 | |||
| 235e59a2d8 | |||
| bc1ffe0716 | |||
| 44497414db | |||
| 96e34fe8f7 | |||
| ceca28d2a3 | |||
| b040571427 | |||
| 49664cc1a0 | |||
| 5e4fd43920 | |||
| 937b947597 | |||
| bb70850421 | |||
| 46511365a7 | |||
| 6d69ac7a98 | |||
| 938937c271 | |||
| cd808416c8 | |||
| a05d0752f6 | |||
| 799609fb21 | |||
| 213fe6a9e9 | |||
| be6a5d5f50 | |||
| 1af29bd7b1 | |||
| ef945e6e99 | |||
| 9528c1e7fc | |||
| fd6e5f465d | |||
| d00125abe1 | |||
| 5d69235a4c | |||
| e93785f885 |
@@ -1,3 +1,96 @@
|
||||
## [23.4.0] 3/30/2025
|
||||
|
||||
### API
|
||||
|
||||
* Expose Zoneserver Compile Metadata ([#4815](https://github.com/EQEmu/Server/pull/4815)) @Akkadius 2025-03-29
|
||||
|
||||
### Bots
|
||||
|
||||
* Charmed Pets were breaking Mob respawns ([#4780](https://github.com/EQEmu/Server/pull/4780)) @nytmyr 2025-03-16
|
||||
* Enraged positioning ([#4789](https://github.com/EQEmu/Server/pull/4789)) @nytmyr 2025-03-29
|
||||
* Fix IsValidSpellTypeBySpellID to account for all types ([#4764](https://github.com/EQEmu/Server/pull/4764)) @nytmyr 2025-03-19
|
||||
* Fix Rule ZonesWithSpawnLimits/ZonesWithForcedSpawnLimits errors ([#4791](https://github.com/EQEmu/Server/pull/4791)) @nytmyr 2025-03-29
|
||||
* Fix rule Bots:FinishBuffing ([#4788](https://github.com/EQEmu/Server/pull/4788)) @nytmyr 2025-03-29
|
||||
* Line of Sight and Mez optimizations and cleanup ([#4746](https://github.com/EQEmu/Server/pull/4746)) @nytmyr 2025-03-29
|
||||
* Prevent bot pets from despawning on #repop ([#4790](https://github.com/EQEmu/Server/pull/4790)) @nytmyr 2025-03-29
|
||||
|
||||
### Code
|
||||
|
||||
* Control flow defaults missed in recent bot updates ([#4817](https://github.com/EQEmu/Server/pull/4817)) @joligario 2025-03-30
|
||||
* Remove Extraneous Time Type in ShowZoneData ([#4806](https://github.com/EQEmu/Server/pull/4806)) @Kinglykrab 2025-03-29
|
||||
* Remove Unused Command Methods ([#4805](https://github.com/EQEmu/Server/pull/4805)) @Kinglykrab 2025-03-29
|
||||
* UCS Member Count ([#4819](https://github.com/EQEmu/Server/pull/4819)) @joligario 2025-03-30
|
||||
|
||||
### Commands
|
||||
|
||||
* Add #show zone_variables ([#4812](https://github.com/EQEmu/Server/pull/4812)) @Akkadius 2025-03-29
|
||||
* Add Instance Support to #zoneshutdown ([#4807](https://github.com/EQEmu/Server/pull/4807)) @Kinglykrab 2025-03-29
|
||||
|
||||
### Crash
|
||||
|
||||
* Fix Rarer World Crash with Player Event Thread Processor ([#4800](https://github.com/EQEmu/Server/pull/4800)) @Akkadius 2025-03-29
|
||||
* Fix Repop Race Condition Crash ([#4814](https://github.com/EQEmu/Server/pull/4814)) @Akkadius 2025-03-29
|
||||
|
||||
### Database
|
||||
|
||||
* Fix Respawn Times Table ([#4802](https://github.com/EQEmu/Server/pull/4802)) @Akkadius 2025-03-29
|
||||
* Wrap PurgeExpiredInstances in a Transaction ([#4824](https://github.com/EQEmu/Server/pull/4824)) @Akkadius 2025-03-30
|
||||
|
||||
### Feature
|
||||
|
||||
* Implement /changename & related script bindings. Clean up #set name ([#4770](https://github.com/EQEmu/Server/pull/4770)) @catapultam-habeo 2025-03-20
|
||||
|
||||
### Fixes
|
||||
|
||||
* AllowFVNoDrop Flag trades ([#4809](https://github.com/EQEmu/Server/pull/4809)) @neckkola 2025-03-27
|
||||
* Fix Instance Creation Race Condition ([#4803](https://github.com/EQEmu/Server/pull/4803)) @Akkadius 2025-03-29
|
||||
* Fix zone crash when attempting to add a disappearing client to hate list. ([#4782](https://github.com/EQEmu/Server/pull/4782)) @zimp-wow 2025-03-19
|
||||
* Globally Reloading Quests when not loaded ([#4813](https://github.com/EQEmu/Server/pull/4813)) @Akkadius 2025-03-29
|
||||
* Instance DZ Creation ([#4823](https://github.com/EQEmu/Server/pull/4823)) @Akkadius 2025-03-30
|
||||
* Zone State Entity Variable Load Pre-Spawn ([#4785](https://github.com/EQEmu/Server/pull/4785)) @Akkadius 2025-03-19
|
||||
* Zone State Position Fix ([#4784](https://github.com/EQEmu/Server/pull/4784)) @Akkadius 2025-03-19
|
||||
* Zone State Variables Load First ([#4798](https://github.com/EQEmu/Server/pull/4798)) @Akkadius 2025-03-29
|
||||
* Zone state edge case with 0 hp ([#4787](https://github.com/EQEmu/Server/pull/4787)) @Akkadius 2025-03-29
|
||||
|
||||
### Instance
|
||||
|
||||
* Clear Respawn Timers on Creation ([#4801](https://github.com/EQEmu/Server/pull/4801)) @Akkadius 2025-03-29
|
||||
|
||||
### Instances
|
||||
|
||||
* Add `expire_at` Column ([#4820](https://github.com/EQEmu/Server/pull/4820)) @Akkadius 2025-03-30
|
||||
|
||||
### Performance
|
||||
|
||||
* Add several database indexes ([#4811](https://github.com/EQEmu/Server/pull/4811)) @Akkadius 2025-03-29
|
||||
* Have World Send Smarter Guild Updates ([#4796](https://github.com/EQEmu/Server/pull/4796)) @Akkadius 2025-03-29
|
||||
* Improve Character Select DB Performance ([#4799](https://github.com/EQEmu/Server/pull/4799)) @Akkadius 2025-03-29
|
||||
* Reduce Adventure S2S chatter ([#4793](https://github.com/EQEmu/Server/pull/4793)) @Akkadius 2025-03-29
|
||||
* Reduce CorpseOwnerOnline S2S Chatter to World ([#4795](https://github.com/EQEmu/Server/pull/4795)) @Akkadius 2025-03-29
|
||||
* Reduce LFGuild Chatter ([#4794](https://github.com/EQEmu/Server/pull/4794)) @Akkadius 2025-03-29
|
||||
* Reduce UpdateWho S2S Chatter to World ([#4792](https://github.com/EQEmu/Server/pull/4792)) @Akkadius 2025-03-29
|
||||
* Send Smarter Emote Packets ([#4818](https://github.com/EQEmu/Server/pull/4818)) @Akkadius 2025-03-30
|
||||
|
||||
### Quest API
|
||||
|
||||
* Add Support for NPC ID and NPC Name Specificity ([#4781](https://github.com/EQEmu/Server/pull/4781)) @Kinglykrab 2025-03-19
|
||||
|
||||
### Reload
|
||||
|
||||
* Add Reload for Maps / Navs ([#4816](https://github.com/EQEmu/Server/pull/4816)) @Akkadius 2025-03-29
|
||||
|
||||
### Zone
|
||||
|
||||
* Zone State Automated Testing and Improvements ([#4808](https://github.com/EQEmu/Server/pull/4808)) @Akkadius 2025-03-30
|
||||
* Zone State Improvements Part 3 ([#4773](https://github.com/EQEmu/Server/pull/4773)) @Akkadius 2025-03-13
|
||||
|
||||
## [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
|
||||
|
||||
@@ -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);
|
||||
@@ -139,6 +141,7 @@ public:
|
||||
bool CheckInstanceExpired(uint16 instance_id);
|
||||
bool CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version, uint32 duration);
|
||||
bool GetUnusedInstanceID(uint16& instance_id);
|
||||
bool TryGetUnusedInstanceID(uint16& instance_id);
|
||||
bool IsGlobalInstance(uint16 instance_id);
|
||||
bool RemoveClientFromInstance(uint16 instance_id, uint32 char_id);
|
||||
bool RemoveClientsFromInstance(uint16 instance_id);
|
||||
|
||||
@@ -6792,7 +6792,7 @@ UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 341) + 5810)
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9304,
|
||||
.description = "2024_12_01_2024_update_guild_bank",
|
||||
.description = "2024_12_01_update_guild_bank",
|
||||
.check = "SHOW COLUMNS FROM `guild_bank` LIKE 'augment_one_id'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
@@ -6914,7 +6914,7 @@ CREATE TABLE `zone_state_spawns` (
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9308,
|
||||
.description = "2025_add_multivalue_support_to_evolving_subtype.sql",
|
||||
.description = "2025_03_29_add_multivalue_support_to_evolving_subtype.sql",
|
||||
.check = "SHOW COLUMNS FROM `items_evolving_details` LIKE 'sub_type'",
|
||||
.condition = "missing",
|
||||
.match = "varchar(200)",
|
||||
@@ -6986,7 +6986,6 @@ ALTER TABLE data_buckets ADD INDEX idx_bot_expires (bot_id, expires);
|
||||
.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
|
||||
},
|
||||
@@ -6998,6 +6997,93 @@ ALTER TABLE zone_state_spawns ADD INDEX idx_instance_id (instance_id);
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
TRUNCATE TABLE zone_state_spawns;
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9315,
|
||||
.description = "2025_03_29_character_tribute_index.sql",
|
||||
.check = "SHOW INDEX FROM character_tribute",
|
||||
.condition = "missing",
|
||||
.match = "idx_character_id",
|
||||
.sql = R"(
|
||||
ALTER TABLE character_tribute ADD INDEX idx_character_id (character_id);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9316,
|
||||
.description = "2025_03_29_player_titlesets_index.sql",
|
||||
.check = "SHOW INDEX FROM player_titlesets",
|
||||
.condition = "missing",
|
||||
.match = "idx_char_id",
|
||||
.sql = R"(
|
||||
ALTER TABLE player_titlesets ADD INDEX idx_char_id (char_id);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9317,
|
||||
.description = "2025_03_29_respawn_times_instance_index.sql",
|
||||
.check = "SHOW INDEX FROM respawn_times",
|
||||
.condition = "missing",
|
||||
.match = "idx_instance_id",
|
||||
.sql = R"(
|
||||
ALTER TABLE respawn_times ADD INDEX idx_instance_id (instance_id);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9318,
|
||||
.description = "2025_03_29_zone_state_spawns_instance_index.sql",
|
||||
.check = "SHOW INDEX FROM zone_state_spawns",
|
||||
.condition = "missing",
|
||||
.match = "idx_instance_id",
|
||||
.sql = R"(
|
||||
ALTER TABLE zone_state_spawns ADD INDEX idx_instance_id (instance_id);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9319,
|
||||
.description = "2025_03_29_data_buckets_expires_index.sql",
|
||||
.check = "SHOW INDEX FROM data_buckets",
|
||||
.condition = "missing",
|
||||
.match = "idx_expires",
|
||||
.sql = R"(
|
||||
CREATE INDEX idx_expires ON data_buckets (expires);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9320,
|
||||
.description = "2025_03_23_add_respawn_times_expire_at.sql",
|
||||
.check = "SHOW COLUMNS FROM `respawn_times` LIKE 'expire_at'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
ALTER TABLE `respawn_times`
|
||||
ADD COLUMN `expire_at` int(11) UNSIGNED NULL DEFAULT 0 AFTER `duration`;
|
||||
|
||||
UPDATE respawn_times set expire_at = `start` + `duration`; -- backfill existing data
|
||||
|
||||
CREATE INDEX `idx_expire_at` ON `respawn_times` (`expire_at`);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
ManifestEntry{
|
||||
.version = 9321,
|
||||
.description = "2025_03_30_instance_list_add_expire_at.sql",
|
||||
.check = "SHOW COLUMNS FROM `instance_list` LIKE 'expire_at'",
|
||||
.condition = "empty",
|
||||
.match = "",
|
||||
.sql = R"(
|
||||
ALTER TABLE `instance_list`
|
||||
ADD COLUMN `expire_at` bigint(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `duration`;
|
||||
|
||||
UPDATE instance_list set expire_at = `start_time` + `duration`; -- backfill existing data
|
||||
|
||||
CREATE INDEX `idx_expire_at` ON `instance_list` (`expire_at`);
|
||||
)",
|
||||
.content_schema_update = false
|
||||
},
|
||||
|
||||
@@ -128,11 +128,35 @@ bool Database::CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version
|
||||
e.version = version;
|
||||
e.start_time = std::time(nullptr);
|
||||
e.duration = duration;
|
||||
e.expire_at = e.start_time + duration;
|
||||
|
||||
return InstanceListRepository::InsertOne(*this, e).id;
|
||||
RespawnTimesRepository::ClearInstanceTimers(*this, e.id);
|
||||
InstanceListRepository::ReplaceOne(*this, e);
|
||||
return instance_id > 0 && e.id;
|
||||
}
|
||||
|
||||
bool Database::GetUnusedInstanceID(uint16 &instance_id)
|
||||
{
|
||||
// attempt to get an unused instance id
|
||||
for (int a = 0; a < 10; a++) {
|
||||
uint16 attempted_id = 0;
|
||||
if (TryGetUnusedInstanceID(attempted_id)) {
|
||||
auto i = InstanceListRepository::NewEntity();
|
||||
i.id = attempted_id;
|
||||
i.notes = "Prefetching";
|
||||
auto n = InstanceListRepository::InsertOne(*this, i);
|
||||
if (n.id > 0) {
|
||||
instance_id = n.id;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance_id = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Database::TryGetUnusedInstanceID(uint16 &instance_id)
|
||||
{
|
||||
uint32 max_reserved_instance_id = RuleI(Instances, ReservedInstances);
|
||||
uint32 max_instance_id = 32000;
|
||||
@@ -537,14 +561,12 @@ void Database::GetCharactersInInstance(uint16 instance_id, std::list<uint32> &ch
|
||||
|
||||
void Database::PurgeExpiredInstances()
|
||||
{
|
||||
/**
|
||||
* Delay purging by a day so that we can continue using adjacent free instance id's
|
||||
* from the table without risking the chance we immediately re-allocate a zone that freshly expired but
|
||||
* has not been fully de-allocated
|
||||
*/
|
||||
auto l = InstanceListRepository::GetWhere(
|
||||
*this,
|
||||
"(start_time + duration) <= (UNIX_TIMESTAMP() - 86400) AND never_expires = 0"
|
||||
fmt::format(
|
||||
"expire_at <= (UNIX_TIMESTAMP() - {}) and expire_at != 0 AND never_expires = 0",
|
||||
RuleI(Instances, ExpireOffsetTimeSeconds)
|
||||
)
|
||||
);
|
||||
if (l.empty()) {
|
||||
return;
|
||||
@@ -555,20 +577,24 @@ void Database::PurgeExpiredInstances()
|
||||
instance_ids.emplace_back(std::to_string(e.id));
|
||||
}
|
||||
|
||||
const auto imploded_instance_ids = Strings::Implode(",", instance_ids);
|
||||
const auto ids = Strings::Implode(",", instance_ids);
|
||||
|
||||
InstanceListRepository::DeleteWhere(*this, fmt::format("id IN ({})", imploded_instance_ids));
|
||||
InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id IN ({})", imploded_instance_ids));
|
||||
RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
||||
SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
||||
CharacterCorpsesRepository::BuryInstances(*this, imploded_instance_ids);
|
||||
DynamicZoneMembersRepository::DeleteByManyInstances(*this, imploded_instance_ids);
|
||||
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
||||
Spawn2DisabledRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
||||
DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id != 0 and instance_id IN ({})", imploded_instance_ids));
|
||||
TransactionBegin();
|
||||
InstanceListRepository::DeleteWhere(*this, fmt::format("id IN ({})", ids));
|
||||
InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id IN ({})", ids));
|
||||
RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
|
||||
SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
|
||||
CharacterCorpsesRepository::BuryInstances(*this, ids);
|
||||
DynamicZoneMembersRepository::DeleteByManyInstances(*this, ids);
|
||||
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
|
||||
Spawn2DisabledRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
|
||||
DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id != 0 and instance_id IN ({})", ids));
|
||||
if (RuleB(Zone, StateSavingOnShutdown)) {
|
||||
ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` IN ({})", imploded_instance_ids));
|
||||
ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` IN ({})", ids));
|
||||
}
|
||||
TransactionCommit();
|
||||
|
||||
LogInfo("Purged [{}] expired instances", l.size());
|
||||
}
|
||||
|
||||
void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
|
||||
@@ -580,6 +606,7 @@ void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
|
||||
|
||||
i.start_time = std::time(nullptr);
|
||||
i.duration = new_duration;
|
||||
i.expire_at = i.start_time + i.duration;
|
||||
|
||||
InstanceListRepository::UpdateOne(*this, i);
|
||||
}
|
||||
|
||||
@@ -58,15 +58,16 @@ uint32_t DynamicZoneBase::CreateInstance()
|
||||
insert_instance.start_time = static_cast<int>(std::chrono::system_clock::to_time_t(m_start_time));
|
||||
insert_instance.duration = static_cast<int>(m_duration.count());
|
||||
insert_instance.never_expires = m_never_expires;
|
||||
insert_instance.expire_at = insert_instance.start_time + insert_instance.duration;
|
||||
|
||||
auto instance = InstanceListRepository::InsertOne(GetDatabase(), insert_instance);
|
||||
if (instance.id == 0)
|
||||
auto instance = InstanceListRepository::ReplaceOne(GetDatabase(), insert_instance);
|
||||
if (!instance)
|
||||
{
|
||||
LogDynamicZones("Failed to create instance [{}] for zone [{}]", unused_instance_id, m_zone_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
m_instance_id = instance.id;
|
||||
m_instance_id = unused_instance_id;
|
||||
|
||||
return m_instance_id;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -906,24 +906,32 @@ bool EQ::ItemInstance::IsSlotAllowed(int16 slot_id) const {
|
||||
|
||||
bool EQ::ItemInstance::IsDroppable(bool recurse) const
|
||||
{
|
||||
if (!m_item)
|
||||
if (!m_item) {
|
||||
return false;
|
||||
}
|
||||
/*if (m_ornamentidfile) // not implemented
|
||||
return false;*/
|
||||
if (m_attuned)
|
||||
if (m_attuned) {
|
||||
return false;
|
||||
/*if (m_item->FVNoDrop != 0) // not implemented
|
||||
return false;*/
|
||||
if (m_item->NoDrop == 0)
|
||||
}
|
||||
|
||||
if (RuleI(World, FVNoDropFlag) == FVNoDropFlagRule::Enabled && m_item->FVNoDrop == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_item->NoDrop == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recurse) {
|
||||
for (auto iter : m_contents) {
|
||||
if (!iter.second)
|
||||
for (auto iter: m_contents) {
|
||||
if (!iter.second) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!iter.second->IsDroppable(recurse))
|
||||
if (!iter.second->IsDroppable(recurse)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ public:
|
||||
uint8_t is_global;
|
||||
uint32_t start_time;
|
||||
uint32_t duration;
|
||||
uint64_t expire_at;
|
||||
uint8_t never_expires;
|
||||
std::string notes;
|
||||
};
|
||||
@@ -43,6 +44,7 @@ public:
|
||||
"is_global",
|
||||
"start_time",
|
||||
"duration",
|
||||
"expire_at",
|
||||
"never_expires",
|
||||
"notes",
|
||||
};
|
||||
@@ -57,6 +59,7 @@ public:
|
||||
"is_global",
|
||||
"start_time",
|
||||
"duration",
|
||||
"expire_at",
|
||||
"never_expires",
|
||||
"notes",
|
||||
};
|
||||
@@ -105,6 +108,7 @@ public:
|
||||
e.is_global = 0;
|
||||
e.start_time = 0;
|
||||
e.duration = 0;
|
||||
e.expire_at = 0;
|
||||
e.never_expires = 0;
|
||||
e.notes = "";
|
||||
|
||||
@@ -149,8 +153,9 @@ public:
|
||||
e.is_global = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
e.start_time = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
|
||||
e.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
|
||||
e.notes = row[7] ? row[7] : "";
|
||||
e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
|
||||
e.never_expires = row[7] ? static_cast<uint8_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.notes = row[8] ? row[8] : "";
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -189,8 +194,9 @@ public:
|
||||
v.push_back(columns[3] + " = " + std::to_string(e.is_global));
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.start_time));
|
||||
v.push_back(columns[5] + " = " + std::to_string(e.duration));
|
||||
v.push_back(columns[6] + " = " + std::to_string(e.never_expires));
|
||||
v.push_back(columns[7] + " = '" + Strings::Escape(e.notes) + "'");
|
||||
v.push_back(columns[6] + " = " + std::to_string(e.expire_at));
|
||||
v.push_back(columns[7] + " = " + std::to_string(e.never_expires));
|
||||
v.push_back(columns[8] + " = '" + Strings::Escape(e.notes) + "'");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -218,6 +224,7 @@ public:
|
||||
v.push_back(std::to_string(e.is_global));
|
||||
v.push_back(std::to_string(e.start_time));
|
||||
v.push_back(std::to_string(e.duration));
|
||||
v.push_back(std::to_string(e.expire_at));
|
||||
v.push_back(std::to_string(e.never_expires));
|
||||
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
||||
|
||||
@@ -255,6 +262,7 @@ public:
|
||||
v.push_back(std::to_string(e.is_global));
|
||||
v.push_back(std::to_string(e.start_time));
|
||||
v.push_back(std::to_string(e.duration));
|
||||
v.push_back(std::to_string(e.expire_at));
|
||||
v.push_back(std::to_string(e.never_expires));
|
||||
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
||||
|
||||
@@ -296,8 +304,9 @@ public:
|
||||
e.is_global = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
e.start_time = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
|
||||
e.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
|
||||
e.notes = row[7] ? row[7] : "";
|
||||
e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
|
||||
e.never_expires = row[7] ? static_cast<uint8_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.notes = row[8] ? row[8] : "";
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -328,8 +337,9 @@ public:
|
||||
e.is_global = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
e.start_time = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
|
||||
e.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
|
||||
e.notes = row[7] ? row[7] : "";
|
||||
e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
|
||||
e.never_expires = row[7] ? static_cast<uint8_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||
e.notes = row[8] ? row[8] : "";
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -410,6 +420,7 @@ public:
|
||||
v.push_back(std::to_string(e.is_global));
|
||||
v.push_back(std::to_string(e.start_time));
|
||||
v.push_back(std::to_string(e.duration));
|
||||
v.push_back(std::to_string(e.expire_at));
|
||||
v.push_back(std::to_string(e.never_expires));
|
||||
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
||||
|
||||
@@ -440,6 +451,7 @@ public:
|
||||
v.push_back(std::to_string(e.is_global));
|
||||
v.push_back(std::to_string(e.start_time));
|
||||
v.push_back(std::to_string(e.duration));
|
||||
v.push_back(std::to_string(e.expire_at));
|
||||
v.push_back(std::to_string(e.never_expires));
|
||||
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
||||
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
class BaseRespawnTimesRepository {
|
||||
public:
|
||||
struct RespawnTimes {
|
||||
int32_t id;
|
||||
int32_t start;
|
||||
int32_t duration;
|
||||
int16_t instance_id;
|
||||
int32_t id;
|
||||
int32_t start;
|
||||
int32_t duration;
|
||||
uint32_t expire_at;
|
||||
int16_t instance_id;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
@@ -36,6 +37,7 @@ public:
|
||||
"id",
|
||||
"start",
|
||||
"duration",
|
||||
"expire_at",
|
||||
"instance_id",
|
||||
};
|
||||
}
|
||||
@@ -46,6 +48,7 @@ public:
|
||||
"id",
|
||||
"start",
|
||||
"duration",
|
||||
"expire_at",
|
||||
"instance_id",
|
||||
};
|
||||
}
|
||||
@@ -90,6 +93,7 @@ public:
|
||||
e.id = 0;
|
||||
e.start = 0;
|
||||
e.duration = 0;
|
||||
e.expire_at = 0;
|
||||
e.instance_id = 0;
|
||||
|
||||
return e;
|
||||
@@ -130,7 +134,8 @@ public:
|
||||
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||
e.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
|
||||
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
||||
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
|
||||
e.expire_at = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
e.instance_id = row[4] ? static_cast<int16_t>(atoi(row[4])) : 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -167,7 +172,8 @@ public:
|
||||
v.push_back(columns[0] + " = " + std::to_string(e.id));
|
||||
v.push_back(columns[1] + " = " + std::to_string(e.start));
|
||||
v.push_back(columns[2] + " = " + std::to_string(e.duration));
|
||||
v.push_back(columns[3] + " = " + std::to_string(e.instance_id));
|
||||
v.push_back(columns[3] + " = " + std::to_string(e.expire_at));
|
||||
v.push_back(columns[4] + " = " + std::to_string(e.instance_id));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@@ -192,6 +198,7 @@ public:
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.start));
|
||||
v.push_back(std::to_string(e.duration));
|
||||
v.push_back(std::to_string(e.expire_at));
|
||||
v.push_back(std::to_string(e.instance_id));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
@@ -225,6 +232,7 @@ public:
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.start));
|
||||
v.push_back(std::to_string(e.duration));
|
||||
v.push_back(std::to_string(e.expire_at));
|
||||
v.push_back(std::to_string(e.instance_id));
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
@@ -262,7 +270,8 @@ public:
|
||||
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||
e.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
|
||||
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
||||
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
|
||||
e.expire_at = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
e.instance_id = row[4] ? static_cast<int16_t>(atoi(row[4])) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -290,7 +299,8 @@ public:
|
||||
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||
e.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
|
||||
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
||||
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
|
||||
e.expire_at = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||
e.instance_id = row[4] ? static_cast<int16_t>(atoi(row[4])) : 0;
|
||||
|
||||
all_entries.push_back(e);
|
||||
}
|
||||
@@ -368,6 +378,7 @@ public:
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.start));
|
||||
v.push_back(std::to_string(e.duration));
|
||||
v.push_back(std::to_string(e.expire_at));
|
||||
v.push_back(std::to_string(e.instance_id));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
@@ -394,6 +405,7 @@ public:
|
||||
v.push_back(std::to_string(e.id));
|
||||
v.push_back(std::to_string(e.start));
|
||||
v.push_back(std::to_string(e.duration));
|
||||
v.push_back(std::to_string(e.expire_at));
|
||||
v.push_back(std::to_string(e.instance_id));
|
||||
|
||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||
|
||||
@@ -7,49 +7,11 @@
|
||||
|
||||
class InstanceListRepository: public BaseInstanceListRepository {
|
||||
public:
|
||||
|
||||
/**
|
||||
* This file was auto generated and can be modified and extended upon
|
||||
*
|
||||
* Base repository methods are automatically
|
||||
* generated in the "base" version of this repository. The base repository
|
||||
* is immutable and to be left untouched, while methods in this class
|
||||
* are used as extension methods for more specific persistence-layer
|
||||
* accessors or mutators.
|
||||
*
|
||||
* Base Methods (Subject to be expanded upon in time)
|
||||
*
|
||||
* Note: Not all tables are designed appropriately to fit functionality with all base methods
|
||||
*
|
||||
* InsertOne
|
||||
* UpdateOne
|
||||
* DeleteOne
|
||||
* FindOne
|
||||
* GetWhere(std::string where_filter)
|
||||
* DeleteWhere(std::string where_filter)
|
||||
* InsertMany
|
||||
* All
|
||||
*
|
||||
* Example custom methods in a repository
|
||||
*
|
||||
* InstanceListRepository::GetByZoneAndVersion(int zone_id, int zone_version)
|
||||
* InstanceListRepository::GetWhereNeverExpires()
|
||||
* InstanceListRepository::GetWhereXAndY()
|
||||
* InstanceListRepository::DeleteWhereXAndY()
|
||||
*
|
||||
* Most of the above could be covered by base methods, but if you as a developer
|
||||
* find yourself re-using logic for other parts of the code, its best to just make a
|
||||
* method that can be re-used easily elsewhere especially if it can use a base repository
|
||||
* method and encapsulate filters there
|
||||
*/
|
||||
|
||||
// Custom extended repository methods here
|
||||
|
||||
static int UpdateDuration(Database& db, uint16 instance_id, uint32_t new_duration)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"UPDATE `{}` SET `duration` = {} WHERE `{}` = {}",
|
||||
"UPDATE `{}` SET `duration` = {}, `expire_at` = (`duration` + `start_time`) WHERE `{}` = {}",
|
||||
TableName(),
|
||||
new_duration,
|
||||
PrimaryKey(),
|
||||
@@ -65,7 +27,7 @@ public:
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
SQL(
|
||||
SELECT ((start_time + duration) - UNIX_TIMESTAMP()) AS `remaining` FROM `{}`
|
||||
SELECT (`expire_at` - UNIX_TIMESTAMP()) AS `remaining` FROM `{}`
|
||||
WHERE `id` = {}
|
||||
),
|
||||
TableName(),
|
||||
|
||||
@@ -8,47 +8,11 @@
|
||||
class RespawnTimesRepository: public BaseRespawnTimesRepository {
|
||||
public:
|
||||
|
||||
/**
|
||||
* This file was auto generated and can be modified and extended upon
|
||||
*
|
||||
* Base repository methods are automatically
|
||||
* generated in the "base" version of this repository. The base repository
|
||||
* is immutable and to be left untouched, while methods in this class
|
||||
* are used as extension methods for more specific persistence-layer
|
||||
* accessors or mutators.
|
||||
*
|
||||
* Base Methods (Subject to be expanded upon in time)
|
||||
*
|
||||
* Note: Not all tables are designed appropriately to fit functionality with all base methods
|
||||
*
|
||||
* InsertOne
|
||||
* UpdateOne
|
||||
* DeleteOne
|
||||
* FindOne
|
||||
* GetWhere(std::string where_filter)
|
||||
* DeleteWhere(std::string where_filter)
|
||||
* InsertMany
|
||||
* All
|
||||
*
|
||||
* Example custom methods in a repository
|
||||
*
|
||||
* RespawnTimesRepository::GetByZoneAndVersion(int zone_id, int zone_version)
|
||||
* RespawnTimesRepository::GetWhereNeverExpires()
|
||||
* RespawnTimesRepository::GetWhereXAndY()
|
||||
* RespawnTimesRepository::DeleteWhereXAndY()
|
||||
*
|
||||
* Most of the above could be covered by base methods, but if you as a developer
|
||||
* find yourself re-using logic for other parts of the code, its best to just make a
|
||||
* method that can be re-used easily elsewhere especially if it can use a base repository
|
||||
* method and encapsulate filters there
|
||||
*/
|
||||
|
||||
// Custom extended repository methods here
|
||||
static void ClearExpiredRespawnTimers(Database& db)
|
||||
{
|
||||
db.QueryDatabase(
|
||||
fmt::format(
|
||||
"DELETE FROM `{}` WHERE (`start` + `duration`) < UNIX_TIMESTAMP(NOW())",
|
||||
"DELETE FROM `{}` WHERE `expire_at` < UNIX_TIMESTAMP(NOW())",
|
||||
TableName()
|
||||
)
|
||||
);
|
||||
@@ -77,6 +41,11 @@ public:
|
||||
|
||||
return ((r.start + r.duration) - time_seconds);
|
||||
}
|
||||
|
||||
static void ClearInstanceTimers(Database &db, int32_t id)
|
||||
{
|
||||
RespawnTimesRepository::DeleteWhere(db, fmt::format("`instance_id` = {}", id));
|
||||
}
|
||||
};
|
||||
|
||||
#endif //EQEMU_RESPAWN_TIMES_REPOSITORY_H
|
||||
|
||||
+14
-10
@@ -54,7 +54,7 @@ RULE_INT(Character, CorpseDecayTime, 604800000, "Time after which the corpse dec
|
||||
RULE_INT(Character, EmptyCorpseDecayTime, 10800000, "Time after which an empty corpse decays (milliseconds) DEFAULT: 10800000 (3 Hours)")
|
||||
RULE_INT(Character, CorpseResTime, 10800000, "Time after which the corpse can no longer be resurrected (milliseconds) DEFAULT: 10800000 (3 Hours)")
|
||||
RULE_INT(Character, DuelCorpseResTime, 600000, "Time before cant res corpse after a duel (milliseconds) DEFAULT: 600000 (10 Minutes)")
|
||||
RULE_INT(Character, CorpseOwnerOnlineTime, 30000, "How often corpse will check if its owner is online DEFAULT: 30000 (30 Seconds)")
|
||||
RULE_INT(Character, CorpseOwnerOnlineCheckTime, 300, "How often corpse will check if its owner is online DEFAULT: 300 (5 minutes)")
|
||||
RULE_BOOL(Character, LeaveCorpses, true, "Setting whether you leave a corpse behind")
|
||||
RULE_BOOL(Character, LeaveNakedCorpses, false, "Setting whether you leave a corpse without items")
|
||||
RULE_INT(Character, MaxDraggedCorpses, 2, "Maximum number of corpses you can drag at once")
|
||||
@@ -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)
|
||||
@@ -260,6 +261,7 @@ RULE_INT(Guild, TributeTime, 600000, "Time in ms for guild tributes. Default is
|
||||
RULE_INT(Guild, TributeTimeRefreshInterval, 180000, "Time in ms to send all guild members a Tribute Time refresh. Default is 3 mins.")
|
||||
RULE_INT(Guild, TributePlatConversionRate, 10, "The conversion rate of platinum donations. Default is 10 guild favor to 1 platinum.")
|
||||
RULE_BOOL(Guild, UseCharacterMaxLevelForGuildTributes, true, "Guild Tributes will adhere to Character:MaxLevel. Default is true.")
|
||||
RULE_BOOL(Guild, EnableLFGuild, false, "Enable the LFGuild system (Requires queryserv)")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Skills)
|
||||
@@ -289,6 +291,7 @@ RULE_BOOL(Pets, ClientPetsUseOwnerNameInLastName, true, "Disable this to keep cl
|
||||
RULE_BOOL(Pets, CanTakeNoDrop, false, "Setting whether anyone can give no-drop items to pets")
|
||||
RULE_INT(Pets, PetTauntRange, 150, "Range at which a pet will taunt targets.")
|
||||
RULE_BOOL(Pets, AlwaysAllowPetRename, false, "Enable this option to allow /changepetname to work without enabling a pet name change via scripts.")
|
||||
RULE_BOOL(Pets, PetsRequireLoS, false, "Whether or not pets require line of sight to be told to attack their target")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(GM)
|
||||
@@ -378,6 +381,7 @@ RULE_BOOL(Zone, StateSaveEntityVariables, true, "Set to true if you want buffs t
|
||||
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_INT(Zone, UpdateWhoTimer, 120, "Seconds between updates to /who list, CLE stale timer")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Map)
|
||||
@@ -387,9 +391,10 @@ RULE_BOOL(Map, MobZVisualDebug, false, "Displays spell effects determining wheth
|
||||
RULE_BOOL(Map, MobPathingVisualDebug, false, "Displays nodes in pathing points in realtime to help with visual debugging")
|
||||
RULE_REAL(Map, FixPathingZMaxDeltaSendTo, 20, "At runtime in SendTo: maximum change in Z to allow the BestZ code to apply")
|
||||
RULE_INT(Map, FindBestZHeightAdjust, 1, "Adds this to the current Z before seeking the best Z position")
|
||||
RULE_BOOL(Map, CheckForLoSCheat, false, "Runs predefined zone checks to check for LoS cheating through doors and such.")
|
||||
RULE_BOOL(Map, EnableLoSCheatExemptions, false, "Enables exemptions for the LoS Cheat check.")
|
||||
RULE_REAL(Map, RangeCheckForLoSCheat, 20.0, "Default 20.0. Range to check if one is within range of a door.")
|
||||
RULE_BOOL(Map, CheckForDoorLoSCheat, true, "Runs LoS checks to prevent cheating through doors.")
|
||||
RULE_BOOL(Map, EnableLoSCheatExemptions, false, "Enables exemptions for the LoS Cheat check. Must modify source to create these.")
|
||||
RULE_REAL(Map, RangeCheckForDoorLoSCheat, 250.0, "Default 250.0. Range to check if a door is blocking LoS from the target.")
|
||||
RULE_STRING(Map, ZonesToCheckDoorCheat, "89,103", "Zones that will check for the door LoS cheat. You can leave it blank to disable, 'all' to check all zones or use a comma-delimited list of zones. Default Sebilis & Chardok")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Pathing)
|
||||
@@ -813,6 +818,7 @@ RULE_INT(Bots, PercentChanceToCastDispel, 75, "The chance for a bot to attempt t
|
||||
RULE_INT(Bots, PercentChanceToCastInCombatBuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
|
||||
RULE_INT(Bots, PercentChanceToCastHateLine, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
|
||||
RULE_INT(Bots, PercentChanceToCastMez, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
|
||||
RULE_INT(Bots, PercentChanceToCastAEMez, 40, "The chance for a bot to attempt to cast the given spell type in combat. Default 40%.")
|
||||
RULE_INT(Bots, PercentChanceToCastSlow, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
|
||||
RULE_INT(Bots, PercentChanceToCastDebuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
|
||||
RULE_INT(Bots, PercentChanceToCastCure, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
|
||||
@@ -824,8 +830,6 @@ RULE_INT(Bots, MinDelayBetweenInCombatCastAttempts, 500, "The minimum delay in m
|
||||
RULE_INT(Bots, MaxDelayBetweenInCombatCastAttempts, 2000, "The maximum delay in milliseconds between cast attempts while in-combat. This is rolled between the min and max. Default 2500ms.")
|
||||
RULE_INT(Bots, MinDelayBetweenOutCombatCastAttempts, 1000, "The minimum delay in milliseconds between cast attempts while out-of-combat. This is rolled between the min and max. Default 1000ms.")
|
||||
RULE_INT(Bots, MaxDelayBetweenOutCombatCastAttempts, 2500, "The maximum delay in milliseconds between cast attempts while out-of-combat. This is rolled between the min and max. Default 2500ms.")
|
||||
RULE_INT(Bots, MezChance, 60, "60 Default. Chance for a bot to attempt to Mez a target after validating it is eligible.")
|
||||
RULE_INT(Bots, AEMezChance, 35, "35 Default. Chance for a bot to attempt to AE Mez targets after validating they are eligible.")
|
||||
RULE_INT(Bots, MezSuccessDelay, 2500, "2500 (2.5 sec) Default. Delay between successful Mez attempts.")
|
||||
RULE_INT(Bots, AEMezSuccessDelay, 5000, "5000 (5 sec) Default. Delay between successful AEMez attempts.")
|
||||
RULE_INT(Bots, MezFailDelay, 1250, "1250 (1.25 sec) Default. Delay between failed Mez attempts.")
|
||||
@@ -835,14 +839,14 @@ 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")
|
||||
RULE_BOOL(Bots, AllowMagicianEpicPet, false, "If enabled, magician bots can summon their epic pets following the rules AllowMagicianEpicPetLevel")
|
||||
RULE_INT(Bots, AllowMagicianEpicPetLevel, 50, "If AllowMagicianEpicPet is enabled, bots can start using their epic pets at this level")
|
||||
RULE_INT(Bots, RequiredMagicianEpicPetItemID, 28034, "If AllowMagicianEpicPet is enabled and this is set, bots will be required to have this item ID equipped to cast their epic. Takes in to account AllowMagicianEpicPetLevel as well. Set to 0 to disable requirement")
|
||||
RULE_STRING(Bots, EpicPetSpellName, "", "'teleport_zone' in the spell to be cast for epic pets. This must be in their spell list to cast.")
|
||||
RULE_STRING(Bots, EpicPetSpellName, "", "'teleport_zone' in the spell to be cast for epic pets. This must be in their usable spell list to cast. Empty uses Manifest Elements - 'SumMageMultiElement'")
|
||||
RULE_INT(Bots, ReclaimEnergySpellID, 331, "Spell ID for reclaim energy when using ^petsettype. Default 331")
|
||||
RULE_BOOL(Bots, UseSpellPulling, true, "If enabled bots will use a spell to pull when within range. Uses PullSpellID.")
|
||||
RULE_INT(Bots, PullSpellID, 5225, "Default 5225 - Throw Stone. Spell that will be cast to pull by bots")
|
||||
@@ -860,8 +864,6 @@ RULE_REAL(Bots, UpperMeleeDistanceMultiplier, 0.55, "Furthest % of the hit box a
|
||||
RULE_REAL(Bots, UpperTauntingMeleeDistanceMultiplier, 0.45, "Furthest % of the hit box a taunting melee bot will get from the target. Default 0.45")
|
||||
RULE_REAL(Bots, UpperMaxMeleeRangeDistanceMultiplier, 0.95, "Furthest % of the hit box a max melee range melee bot will get from the target. Default 0.95")
|
||||
RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "If true, when bots are at max melee distance, special abilities including taunt will be disabled. Default True.")
|
||||
RULE_BOOL(Bots, TauntingBotsFollowTopHate, true, "True Default. If true, bots that are taunting will attempt to stick with whoever currently is top hate.")
|
||||
RULE_INT(Bots, DistanceTauntingBotsStickMainHate, 10, "If TauntingBotsFollowTopHate is enabled, this is the distance bots will try to stick to whoever currently is Top Hate.")
|
||||
RULE_INT(Bots, MinJitterTimer, 500, "Minimum ms between bot movement jitter checks.")
|
||||
RULE_INT(Bots, MaxJitterTimer, 2500, "Maximum ms between bot movement jitter checks. Set to 0 to disable timer checks.")
|
||||
RULE_BOOL(Bots, PreventBotCampOnFD, true, "True Default. If true, players will not be able to camp bots while feign death.")
|
||||
@@ -902,6 +904,7 @@ RULE_STRING(Bots, ZonesWithForcedSpawnLimits, "", "Comma-delimited list of zones
|
||||
RULE_STRING(Bots, ZoneForcedSpawnLimits, "", "Comma-delimited list of forced spawn limits for zones.")
|
||||
RULE_INT(Bots, AICastSpellTypeDelay, 100, "Delay in milliseconds between AI cast attempts for each spell type. Default 100ms")
|
||||
RULE_INT(Bots, AICastSpellTypeHeldDelay, 2500, "Delay in milliseconds between AI cast attempts for each spell type that is held or disabled. Default 2500ms (2.5s)")
|
||||
RULE_BOOL(Bots, BotsRequireLoS, true, "Whether or not bots require line of sight to be told to attack their target")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Chat)
|
||||
@@ -1093,6 +1096,7 @@ RULE_CATEGORY(Instances)
|
||||
RULE_INT(Instances, ReservedInstances, 100, "Number of instance IDs which are reserved for globals. This value should not be changed while a server is running")
|
||||
RULE_BOOL(Instances, RecycleInstanceIds, true, "Setting whether free instance IDs should be recycled to prevent them from gradually running out at 32k")
|
||||
RULE_INT(Instances, GuildHallExpirationDays, 90, "Amount of days before a Guild Hall instance expires")
|
||||
RULE_INT(Instances, ExpireOffsetTimeSeconds, 3600, "Amount of seconds to beyond instance expiration time we wait to purge the entry from the database. (Default: 1 Hour)")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Expedition)
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace ServerReload {
|
||||
LevelEXPMods,
|
||||
Logs,
|
||||
Loot,
|
||||
Maps,
|
||||
Merchants,
|
||||
NPCEmotes,
|
||||
NPCSpells,
|
||||
@@ -61,6 +62,7 @@ namespace ServerReload {
|
||||
"Level EXP Mods",
|
||||
"Logs",
|
||||
"Loot",
|
||||
"Maps",
|
||||
"Merchants",
|
||||
"NPC Emotes",
|
||||
"NPC Spells",
|
||||
|
||||
+49
-49
@@ -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) &&
|
||||
@@ -2796,18 +2808,6 @@ bool IsLichSpell(uint16 spell_id)
|
||||
);
|
||||
}
|
||||
|
||||
bool IsValidSpellAndLoS(uint32 spell_id, bool has_los) {
|
||||
if (!IsValidSpell(spell_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!has_los && IsTargetRequiredForSpell(spell_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsInstantHealSpell(uint32 spell_id) {
|
||||
if (!IsValidSpell(spell_id)) {
|
||||
return false;
|
||||
|
||||
+5
-3
@@ -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
|
||||
@@ -899,8 +900,8 @@ const uint32 SPELL_TYPES_BENEFICIAL = (SpellType_Heal | SpellType_Buff | SpellTy
|
||||
const uint32 SPELL_TYPES_INNATE = (SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root);
|
||||
|
||||
// Bot related functions
|
||||
bool IsBotSpellTypeDetrimental (uint16 spell_type);
|
||||
bool IsBotSpellTypeBeneficial (uint16 spell_type);
|
||||
bool IsBotSpellTypeDetrimental(uint16 spell_type);
|
||||
bool IsBotSpellTypeBeneficial(uint16 spell_type);
|
||||
bool BotSpellTypeUsesTargetSettings(uint16 spell_type);
|
||||
bool IsBotSpellTypeInnate (uint16 spell_type);
|
||||
bool IsAEBotSpellType(uint16 spell_type);
|
||||
@@ -916,6 +917,8 @@ bool IsCommandedBotSpellType(uint16 spell_type);
|
||||
bool IsPullingBotSpellType(uint16 spell_type);
|
||||
uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id);
|
||||
uint16 GetPetBotSpellType(uint16 spell_type);
|
||||
bool IsBotBuffSpellType(uint16 spell_type);
|
||||
bool BotRequiresLoSToCast(uint16 spell_type, uint16 spell_id);
|
||||
|
||||
// These should not be used to determine spell category..
|
||||
// They are a graphical affects (effects?) index only
|
||||
@@ -1813,7 +1816,6 @@ bool IsEffectInSpell(uint16 spell_id, int effect_id);
|
||||
uint16 GetSpellTriggerSpellID(uint16 spell_id, int effect_id);
|
||||
bool IsBlankSpellEffect(uint16 spell_id, int effect_index);
|
||||
bool IsValidSpell(uint32 spell_id);
|
||||
bool IsValidSpellAndLoS(uint32 spell_id, bool has_los = true);
|
||||
bool IsSummonSpell(uint16 spell_id);
|
||||
bool IsDamageSpell(uint16 spell_id);
|
||||
bool IsAnyDamageSpell(uint16 spell_id);
|
||||
|
||||
+40
-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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -708,3 +468,31 @@ uint16 GetPetBotSpellType(uint16 spell_type) {
|
||||
|
||||
return spell_type;
|
||||
}
|
||||
|
||||
bool IsBotBuffSpellType(uint16 spell_type) {
|
||||
switch (spell_type) {
|
||||
case BotSpellTypes::Buff:
|
||||
case BotSpellTypes::PetBuffs:
|
||||
case BotSpellTypes::ResistBuffs:
|
||||
case BotSpellTypes::PetResistBuffs:
|
||||
case BotSpellTypes::DamageShields:
|
||||
case BotSpellTypes::PetDamageShields:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BotRequiresLoSToCast(uint16 spell_type, uint16 spell_id) {
|
||||
if (!BotSpellTypeRequiresTarget(spell_type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsTargetRequiredForSpell(spell_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -196,3 +196,25 @@ const uint32 Timer::SetCurrentTime()
|
||||
return current_time;
|
||||
}
|
||||
|
||||
const uint32 Timer::RollForward(uint32 seconds)
|
||||
{
|
||||
struct timeval read_time{};
|
||||
uint32 this_time;
|
||||
|
||||
gettimeofday(&read_time, nullptr);
|
||||
this_time = read_time.tv_sec * 1000 + read_time.tv_usec / 1000;
|
||||
|
||||
if (last_time == 0) {
|
||||
current_time = 0;
|
||||
}
|
||||
else {
|
||||
current_time += this_time - last_time;
|
||||
}
|
||||
|
||||
last_time = this_time;
|
||||
|
||||
// Roll forward the specified number of seconds (converted to milliseconds)
|
||||
current_time += seconds * 1000;
|
||||
|
||||
return current_time;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ public:
|
||||
inline uint32 GetDuration() { return(timer_time); }
|
||||
|
||||
static const uint32 SetCurrentTime();
|
||||
static const uint32 RollForward(uint32 seconds);
|
||||
static const uint32 GetCurrentTime();
|
||||
static const uint32 GetTimeSeconds();
|
||||
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@
|
||||
|
||||
// Build variables
|
||||
// these get injected during the build pipeline
|
||||
#define CURRENT_VERSION "23.3.3-dev" // always append -dev to the current version for custom-builds
|
||||
#define CURRENT_VERSION "23.4.0-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 9314
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9321
|
||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
|
||||
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eqemu-server",
|
||||
"version": "23.3.3",
|
||||
"version": "23.4.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EQEmu/Server.git"
|
||||
|
||||
@@ -177,7 +177,7 @@ int main()
|
||||
}
|
||||
|
||||
if (player_event_process_timer.Check()) {
|
||||
std::jthread player_event_thread(&PlayerEventLogs::Process, &player_event_logs);
|
||||
player_event_logs.Process();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+2
-2
@@ -177,7 +177,7 @@ void ChatChannelList::SendAllChannels(Client *c) {
|
||||
|
||||
std::string Message;
|
||||
|
||||
char CountString[10];
|
||||
char CountString[13];
|
||||
|
||||
while(iterator.MoreElements()) {
|
||||
|
||||
@@ -408,7 +408,7 @@ void ChatChannel::SendChannelMembers(Client *c) {
|
||||
|
||||
if(!c) return;
|
||||
|
||||
char CountString[10];
|
||||
char CountString[13];
|
||||
|
||||
sprintf(CountString, "(%i)", MemberCount(c->GetAccountStatus()));
|
||||
|
||||
|
||||
@@ -746,3 +746,6 @@ OP_TradeSkillRecipeInspect=0x4f7e
|
||||
OP_InvokeChangePetNameImmediate=0x046d
|
||||
OP_InvokeChangePetName=0x4506
|
||||
OP_ChangePetName=0x5dab
|
||||
|
||||
OP_InvokeNameChangeImmediate=0x4fe2
|
||||
OP_InvokeNameChangeLazy=0x2f2e
|
||||
|
||||
@@ -57,6 +57,7 @@ echo "# Running NPC hand-in tests"
|
||||
./bin/zone tests:npc-handins 2>&1 | tee test_output.log
|
||||
./bin/zone tests:npc-handins-multiquest 2>&1 | tee -a test_output.log
|
||||
./bin/zone tests:databuckets 2>&1 | tee -a test_output.log
|
||||
./bin/zone tests:zone-state 2>&1 | tee -a test_output.log
|
||||
|
||||
if grep -E -q "QueryErr|Error|FAILED" test_output.log; then
|
||||
echo "Error found in test output! Failing build."
|
||||
|
||||
@@ -1850,3 +1850,72 @@ std::map<uint32, ClientListEntry *> ClientList::GetGuildClientsWithTributeOptIn(
|
||||
}
|
||||
return guild_members;
|
||||
}
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
|
||||
{
|
||||
std::vector<uint32_t> zone_server_ids;
|
||||
std::unordered_set<uint32_t> seen_ids;
|
||||
|
||||
LinkedListIterator<ClientListEntry *> iterator(clientlist);
|
||||
|
||||
iterator.Reset();
|
||||
while (iterator.MoreElements()) {
|
||||
ClientListEntry *cle = iterator.GetData();
|
||||
|
||||
if (cle->Online() != CLE_Status::InZone) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cle->Server()) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cle->GuildID() == guild_id) {
|
||||
uint32_t id = cle->Server()->GetID();
|
||||
if (seen_ids.insert(id).second) {
|
||||
zone_server_ids.emplace_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
iterator.Advance();
|
||||
}
|
||||
|
||||
return zone_server_ids;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> ClientList::GetZoneServersWithGMs()
|
||||
{
|
||||
std::vector<uint32_t> zone_server_ids;
|
||||
std::unordered_set<uint32_t> seen_ids;
|
||||
LinkedListIterator<ClientListEntry *> iterator(clientlist);
|
||||
|
||||
iterator.Reset();
|
||||
while (iterator.MoreElements()) {
|
||||
ClientListEntry *cle = iterator.GetData();
|
||||
|
||||
if (cle->Online() != CLE_Status::InZone) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cle->Server()) {
|
||||
iterator.Advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cle->Admin() > 0) {
|
||||
uint32_t id = cle->Server()->GetID();
|
||||
if (seen_ids.insert(id).second) {
|
||||
zone_server_ids.emplace_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
iterator.Advance();
|
||||
}
|
||||
|
||||
return zone_server_ids;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,8 @@ public:
|
||||
void CLCheckStale();
|
||||
void CLEKeepAlive(uint32 numupdates, uint32* wid);
|
||||
void CLEAdd(uint32 login_server_id, const char* login_server_name, const char* login_name, const char* login_key, int16 world_admin = AccountStatus::Player, uint32 ip_address = 0, uint8 is_local=0);
|
||||
std::vector<uint32_t> GetGuildZoneServers(uint32 guild_id);
|
||||
std::vector<uint32_t> GetZoneServersWithGMs();
|
||||
void UpdateClientGuild(uint32 char_id, uint32 guild_id);
|
||||
bool IsAccountInGame(uint32 iLSID);
|
||||
|
||||
|
||||
@@ -137,8 +137,11 @@ void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining)
|
||||
m_expire_time = now + new_remaining;
|
||||
m_duration = std::chrono::duration_cast<std::chrono::seconds>(m_expire_time - m_start_time);
|
||||
|
||||
InstanceListRepository::UpdateDuration(database,
|
||||
GetInstanceID(), static_cast<uint32_t>(m_duration.count()));
|
||||
InstanceListRepository::UpdateDuration(
|
||||
database,
|
||||
GetInstanceID(),
|
||||
static_cast<uint32_t>(m_duration.count())
|
||||
);
|
||||
|
||||
SendZonesDurationUpdate(); // update zone caches and actual instance's timer
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ void callGetZoneList(Json::Value &response)
|
||||
row["client_address"] = zone->GetCAddress();
|
||||
row["client_local_address"] = zone->GetCLocalAddress();
|
||||
row["client_port"] = zone->GetCPort();
|
||||
row["compile_version"] = zone->GetCurrentVersion();
|
||||
row["compile_date"] = zone->GetCompileDate();
|
||||
row["compile_time"] = zone->GetCompileTime();
|
||||
row["id"] = zone->GetID();
|
||||
row["instance_id"] = zone->GetInstanceID();
|
||||
|
||||
+11
-5
@@ -381,11 +381,19 @@ int main(int argc, char **argv)
|
||||
}
|
||||
);
|
||||
|
||||
Timer player_event_process_timer(1000);
|
||||
if (player_event_logs.LoadDatabaseConnection()) {
|
||||
player_event_logs.Init();
|
||||
}
|
||||
|
||||
auto event_log_processor = std::jthread([](const std::stop_token& stoken) {
|
||||
while (!stoken.stop_requested()) {
|
||||
if (!RuleB(Logging, PlayerEventsQSProcess)) {
|
||||
player_event_logs.Process();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
}
|
||||
});
|
||||
|
||||
auto loop_fn = [&](EQ::Timer* t) {
|
||||
Timer::SetCurrentTime();
|
||||
|
||||
@@ -448,10 +456,6 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (player_event_process_timer.Check()) {
|
||||
std::jthread event_thread(&PlayerEventLogs::Process, &player_event_logs);
|
||||
}
|
||||
|
||||
if (PurgeInstanceTimer.Check()) {
|
||||
database.PurgeExpiredInstances();
|
||||
database.PurgeAllDeletedDataBuckets();
|
||||
@@ -502,6 +506,8 @@ int main(int argc, char **argv)
|
||||
|
||||
EQ::EventLoop::Get().Run();
|
||||
|
||||
event_log_processor.request_stop();
|
||||
|
||||
LogInfo("World main loop completed");
|
||||
LogInfo("Shutting down zone connections (if any)");
|
||||
zoneserver_list.KillAll();
|
||||
|
||||
+22
-17
@@ -50,7 +50,7 @@ void WorldGuildManager::SendGuildRefresh(uint32 guild_id, bool name, bool motd,
|
||||
s->motd_change = motd;
|
||||
s->rank_change = rank;
|
||||
s->relation_change = relation;
|
||||
zoneserver_list.SendPacket(pack);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
|
||||
safe_delete(pack);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ void WorldGuildManager::SendCharRefresh(uint32 old_guild_id, uint32 guild_id, ui
|
||||
s->guild_id = guild_id;
|
||||
s->old_guild_id = old_guild_id;
|
||||
s->char_id = charid;
|
||||
zoneserver_list.SendPacket(pack);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
|
||||
safe_delete(pack);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ void WorldGuildManager::SendGuildDelete(uint32 guild_id) {
|
||||
auto pack = new ServerPacket(ServerOP_DeleteGuild, sizeof(ServerGuildID_Struct));
|
||||
ServerGuildID_Struct *s = (ServerGuildID_Struct *) pack->pBuffer;
|
||||
s->guild_id = guild_id;
|
||||
zoneserver_list.SendPacket(pack);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
|
||||
safe_delete(pack);
|
||||
}
|
||||
|
||||
@@ -85,15 +85,14 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
||||
ServerGuildRefresh_Struct *s = (ServerGuildRefresh_Struct *) pack->pBuffer;
|
||||
LogGuilds("Received and broadcasting guild refresh for [{}], changes: name=[{}], motd=[{}], rank=d, relation=[{}]", s->guild_id, s->name_change, s->motd_change, s->rank_change, s->relation_change);
|
||||
|
||||
//broadcast this packet to all zones.
|
||||
zoneserver_list.SendPacket(pack);
|
||||
|
||||
//preform a local refresh.
|
||||
if(!RefreshGuild(s->guild_id)) {
|
||||
LogGuilds("Unable to preform local refresh on guild [{}]", s->guild_id);
|
||||
//can we do anything?
|
||||
BaseGuildManager::RefreshGuild(s->guild_id);
|
||||
}
|
||||
|
||||
//broadcast this packet to all zones.
|
||||
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -108,7 +107,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
||||
|
||||
//broadcast this update to any zone with a member in this guild.
|
||||
//because im sick of this not working, sending it to all zones, just spends a bit more bandwidth.
|
||||
zoneserver_list.SendPacket(pack);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -147,7 +146,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
||||
auto s = (ServerGuildID_Struct *)pack->pBuffer;
|
||||
RefreshGuild(s->guild_id);
|
||||
|
||||
zoneserver_list.SendPacket(pack);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
|
||||
break;
|
||||
}
|
||||
case ServerOP_GuildPermissionUpdate:
|
||||
@@ -179,7 +178,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
||||
sg->function_value
|
||||
);
|
||||
|
||||
zoneserver_list.SendPacketToBootedZones(pack);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(sg->guild_id, pack);
|
||||
}
|
||||
else {
|
||||
LogError("World Received ServerOP_GuildPermissionUpdate for guild [{}] function id {} with value of {} but guild could not be found.",
|
||||
@@ -213,7 +212,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
||||
rnc->rank,
|
||||
rnc->rank_name
|
||||
);
|
||||
zoneserver_list.SendPacketToBootedZones(pack);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(rnc->guild_id, pack);
|
||||
}
|
||||
else {
|
||||
LogError("World Received ServerOP_GuildRankNameChange from zone for guild [{}] rank id {} with new name of {} but could not find guild.",
|
||||
@@ -230,13 +229,13 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
||||
case ServerOP_GuildChannel:
|
||||
case ServerOP_GuildURL:
|
||||
case ServerOP_GuildMemberRemove:
|
||||
case ServerOP_GuildSendGuildList:
|
||||
case ServerOP_GuildMembersList:
|
||||
{
|
||||
zoneserver_list.SendPacketToBootedZones(pack);
|
||||
auto in = (ServerOP_GuildMessage_Struct *) pack->pBuffer;
|
||||
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, pack);
|
||||
break;
|
||||
}
|
||||
case ServerOP_GuildMemberAdd:
|
||||
case ServerOP_GuildMemberAdd:
|
||||
{
|
||||
auto in = (ServerOP_GuildMessage_Struct *)pack->pBuffer;
|
||||
auto guild = GetGuildByGuildID(in->guild_id);
|
||||
@@ -244,9 +243,15 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
||||
BaseGuildManager::RefreshGuild(in->guild_id);
|
||||
}
|
||||
|
||||
zoneserver_list.SendPacketToBootedZones(pack);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, pack);
|
||||
break;
|
||||
}
|
||||
case ServerOP_GuildSendGuildList: {
|
||||
auto in = (ServerOP_GuildMessage_Struct *) pack->pBuffer;
|
||||
zoneserver_list.SendPacketToBootedZones(pack);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
LogGuilds("Unknown packet {:#04x} received from zone??", pack->opcode);
|
||||
break;
|
||||
@@ -451,6 +456,6 @@ void WorldGuildManager::SendGuildTributeFavorAndTimer(uint32 guild_id, uint32 fa
|
||||
data->tribute_timer = time;
|
||||
data->trophy_timer = 0;
|
||||
|
||||
zoneserver_list.SendPacketToBootedZones(sp);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(guild_id, sp);
|
||||
safe_delete(sp)
|
||||
}
|
||||
|
||||
+246
-282
@@ -29,6 +29,10 @@
|
||||
#include "../common/repositories/inventory_repository.h"
|
||||
#include "../common/repositories/criteria/content_filter_criteria.h"
|
||||
#include "../common/zone_store.h"
|
||||
#include "../common/repositories/character_data_repository.h"
|
||||
#include "../common/repositories/character_bind_repository.h"
|
||||
#include "../common/repositories/character_material_repository.h"
|
||||
#include "../common/repositories/start_zones_repository.h"
|
||||
|
||||
WorldDatabase database;
|
||||
WorldDatabase content_db;
|
||||
@@ -50,187 +54,177 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o
|
||||
character_limit = 8;
|
||||
}
|
||||
|
||||
std::string character_list_query = fmt::format(
|
||||
SQL(
|
||||
SELECT
|
||||
`id`,
|
||||
`name`,
|
||||
`gender`,
|
||||
`race`,
|
||||
`class`,
|
||||
`level`,
|
||||
`deity`,
|
||||
`last_login`,
|
||||
`time_played`,
|
||||
`hair_color`,
|
||||
`beard_color`,
|
||||
`eye_color_1`,
|
||||
`eye_color_2`,
|
||||
`hair_style`,
|
||||
`beard`,
|
||||
`face`,
|
||||
`drakkin_heritage`,
|
||||
`drakkin_tattoo`,
|
||||
`drakkin_details`,
|
||||
`zone_id`
|
||||
FROM
|
||||
`character_data`
|
||||
WHERE
|
||||
`account_id` = {}
|
||||
AND
|
||||
`deleted_at` IS NULL
|
||||
ORDER BY `name`
|
||||
LIMIT {}
|
||||
),
|
||||
account_id,
|
||||
character_limit
|
||||
auto characters = CharacterDataRepository::GetWhere(
|
||||
database,
|
||||
fmt::format(
|
||||
"`account_id` = {} AND `deleted_at` IS NULL ORDER BY `name` LIMIT {}",
|
||||
account_id,
|
||||
character_limit
|
||||
)
|
||||
);
|
||||
|
||||
auto results = database.QueryDatabase(character_list_query);
|
||||
size_t character_count = results.RowCount();
|
||||
if (character_count == 0) {
|
||||
size_t character_count = characters.size();
|
||||
if (characters.empty()) {
|
||||
*out_app = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct));
|
||||
CharacterSelect_Struct *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer;
|
||||
cs->CharCount = 0;
|
||||
auto *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer;
|
||||
cs->CharCount = 0;
|
||||
cs->TotalChars = character_limit;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> character_ids;
|
||||
for (auto &e: characters) {
|
||||
character_ids.push_back(e.id);
|
||||
}
|
||||
|
||||
const auto& inventories = InventoryRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`character_id` IN ({}) AND `slot_id` BETWEEN {} AND {}",
|
||||
Strings::Join(character_ids, ","),
|
||||
EQ::invslot::slotHead,
|
||||
EQ::invslot::slotFeet
|
||||
)
|
||||
);
|
||||
|
||||
const auto& character_binds = CharacterBindRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`id` IN ({}) ORDER BY id, slot",
|
||||
Strings::Join(character_ids, ",")
|
||||
)
|
||||
);
|
||||
|
||||
const auto& character_materials = CharacterMaterialRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`id` IN ({}) ORDER BY id, slot",
|
||||
Strings::Join(character_ids, ",")
|
||||
)
|
||||
);
|
||||
|
||||
size_t packet_size = sizeof(CharacterSelect_Struct) + (sizeof(CharacterSelectEntry_Struct) * character_count);
|
||||
*out_app = new EQApplicationPacket(OP_SendCharInfo, packet_size);
|
||||
|
||||
unsigned char *buff_ptr = (*out_app)->pBuffer;
|
||||
CharacterSelect_Struct *cs = (CharacterSelect_Struct *) buff_ptr;
|
||||
auto *cs = (CharacterSelect_Struct *) buff_ptr;
|
||||
|
||||
cs->CharCount = character_count;
|
||||
cs->CharCount = character_count;
|
||||
cs->TotalChars = character_limit;
|
||||
|
||||
buff_ptr += sizeof(CharacterSelect_Struct);
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
CharacterSelectEntry_Struct *p_character_select_entry_struct = (CharacterSelectEntry_Struct *) buff_ptr;
|
||||
PlayerProfile_Struct pp;
|
||||
EQ::InventoryProfile inventory_profile;
|
||||
for (auto &e: characters) {
|
||||
auto *cse = (CharacterSelectEntry_Struct *) buff_ptr;
|
||||
PlayerProfile_Struct pp;
|
||||
EQ::InventoryProfile inv;
|
||||
|
||||
pp.SetPlayerProfileVersion(EQ::versions::ConvertClientVersionToMobVersion(client_version));
|
||||
inventory_profile.SetInventoryVersion(client_version);
|
||||
inventory_profile.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support
|
||||
inv.SetInventoryVersion(client_version);
|
||||
inv.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support
|
||||
|
||||
uint32 character_id = Strings::ToUnsignedInt(row[0]);
|
||||
uint8 has_home = 0;
|
||||
uint8 has_bind = 0;
|
||||
uint32 character_id = e.id;
|
||||
uint8 has_home = 0;
|
||||
uint8 has_bind = 0;
|
||||
|
||||
memset(&pp, 0, sizeof(PlayerProfile_Struct));
|
||||
memset(p_character_select_entry_struct->Name, 0, sizeof(p_character_select_entry_struct->Name));
|
||||
strcpy(p_character_select_entry_struct->Name, row[1]);
|
||||
p_character_select_entry_struct->Class = (uint8) Strings::ToUnsignedInt(row[4]);
|
||||
p_character_select_entry_struct->Race = (uint32) Strings::ToUnsignedInt(row[3]);
|
||||
p_character_select_entry_struct->Level = (uint8) Strings::ToUnsignedInt(row[5]);
|
||||
p_character_select_entry_struct->ShroudClass = p_character_select_entry_struct->Class;
|
||||
p_character_select_entry_struct->ShroudRace = p_character_select_entry_struct->Race;
|
||||
p_character_select_entry_struct->Zone = (uint16) Strings::ToUnsignedInt(row[19]);
|
||||
p_character_select_entry_struct->Instance = 0;
|
||||
p_character_select_entry_struct->Gender = (uint8) Strings::ToUnsignedInt(row[2]);
|
||||
p_character_select_entry_struct->Face = (uint8) Strings::ToUnsignedInt(row[15]);
|
||||
memset(cse->Name, 0, sizeof(cse->Name));
|
||||
strcpy(cse->Name, e.name.c_str());
|
||||
cse->Class = e.class_;
|
||||
cse->Race = e.race;
|
||||
cse->Level = e.level;
|
||||
cse->ShroudClass = cse->Class;
|
||||
cse->ShroudRace = cse->Race;
|
||||
cse->Zone = e.zone_id;
|
||||
cse->Instance = 0;
|
||||
cse->Gender = e.gender;
|
||||
cse->Face = e.face;
|
||||
|
||||
for (uint32 material_slot = 0; material_slot < EQ::textures::materialCount; material_slot++) {
|
||||
p_character_select_entry_struct->Equip[material_slot].Material = 0;
|
||||
p_character_select_entry_struct->Equip[material_slot].Unknown1 = 0;
|
||||
p_character_select_entry_struct->Equip[material_slot].EliteModel = 0;
|
||||
p_character_select_entry_struct->Equip[material_slot].HerosForgeModel = 0;
|
||||
p_character_select_entry_struct->Equip[material_slot].Unknown2 = 0;
|
||||
p_character_select_entry_struct->Equip[material_slot].Color = 0;
|
||||
for (auto &s: cse->Equip) {
|
||||
s.Material = 0;
|
||||
s.Unknown1 = 0;
|
||||
s.EliteModel = 0;
|
||||
s.HerosForgeModel = 0;
|
||||
s.Unknown2 = 0;
|
||||
s.Color = 0;
|
||||
}
|
||||
|
||||
p_character_select_entry_struct->Unknown15 = 0xFF;
|
||||
p_character_select_entry_struct->Unknown19 = 0xFF;
|
||||
p_character_select_entry_struct->DrakkinTattoo = (uint32) Strings::ToInt(row[17]);
|
||||
p_character_select_entry_struct->DrakkinDetails = (uint32) Strings::ToInt(row[18]);
|
||||
p_character_select_entry_struct->Deity = (uint32) Strings::ToInt(row[6]);
|
||||
p_character_select_entry_struct->PrimaryIDFile = 0; // Processed Below
|
||||
p_character_select_entry_struct->SecondaryIDFile = 0; // Processed Below
|
||||
p_character_select_entry_struct->HairColor = (uint8) Strings::ToInt(row[9]);
|
||||
p_character_select_entry_struct->BeardColor = (uint8) Strings::ToInt(row[10]);
|
||||
p_character_select_entry_struct->EyeColor1 = (uint8) Strings::ToInt(row[11]);
|
||||
p_character_select_entry_struct->EyeColor2 = (uint8) Strings::ToInt(row[12]);
|
||||
p_character_select_entry_struct->HairStyle = (uint8) Strings::ToInt(row[13]);
|
||||
p_character_select_entry_struct->Beard = (uint8) Strings::ToInt(row[14]);
|
||||
p_character_select_entry_struct->GoHome = 0; // Processed Below
|
||||
p_character_select_entry_struct->Tutorial = 0; // Processed Below
|
||||
p_character_select_entry_struct->DrakkinHeritage = (uint32) Strings::ToInt(row[16]);
|
||||
p_character_select_entry_struct->Unknown1 = 0;
|
||||
p_character_select_entry_struct->Enabled = 1;
|
||||
p_character_select_entry_struct->LastLogin = (uint32) Strings::ToInt(row[7]); // RoF2 value: 1212696584
|
||||
p_character_select_entry_struct->Unknown2 = 0;
|
||||
cse->Unknown15 = 0xFF;
|
||||
cse->Unknown19 = 0xFF;
|
||||
cse->DrakkinTattoo = e.drakkin_tattoo;
|
||||
cse->DrakkinDetails = e.drakkin_details;
|
||||
cse->Deity = e.deity;
|
||||
cse->PrimaryIDFile = 0; // Processed Below
|
||||
cse->SecondaryIDFile = 0; // Processed Below
|
||||
cse->HairColor = e.hair_color;
|
||||
cse->BeardColor = e.beard_color;
|
||||
cse->EyeColor1 = e.eye_color_1;
|
||||
cse->EyeColor2 = e.eye_color_2;
|
||||
cse->HairStyle = e.hair_style;
|
||||
cse->Beard = e.beard;
|
||||
cse->GoHome = 0; // Processed Below
|
||||
cse->Tutorial = 0; // Processed Below
|
||||
cse->DrakkinHeritage = e.drakkin_heritage;
|
||||
cse->Unknown1 = 0;
|
||||
cse->Enabled = 1;
|
||||
cse->LastLogin = e.last_login; // RoF2 value: 1212696584
|
||||
cse->Unknown2 = 0;
|
||||
|
||||
if (RuleB(World, EnableReturnHomeButton)) {
|
||||
int now = time(nullptr);
|
||||
if ((now - Strings::ToInt(row[7])) >= RuleI(World, MinOfflineTimeToReturnHome))
|
||||
p_character_select_entry_struct->GoHome = 1;
|
||||
if (now - e.last_login >= RuleI(World, MinOfflineTimeToReturnHome)) {
|
||||
cse->GoHome = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (RuleB(World, EnableTutorialButton) && (p_character_select_entry_struct->Level <= RuleI(World, MaxLevelForTutorial)))
|
||||
p_character_select_entry_struct->Tutorial = 1;
|
||||
if (RuleB(World, EnableTutorialButton) && (cse->Level <= RuleI(World, MaxLevelForTutorial))) {
|
||||
cse->Tutorial = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind
|
||||
*/
|
||||
character_list_query = fmt::format(
|
||||
SQL(
|
||||
SELECT
|
||||
`zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`
|
||||
FROM
|
||||
`character_bind`
|
||||
WHERE
|
||||
`id` = {}
|
||||
LIMIT 5
|
||||
),
|
||||
character_id
|
||||
);
|
||||
auto results_bind = database.QueryDatabase(character_list_query);
|
||||
auto bind_count = results_bind.RowCount();
|
||||
for (auto row_b = results_bind.begin(); row_b != results_bind.end(); ++row_b) {
|
||||
if (row_b[6] && Strings::ToInt(row_b[6]) == 4) {
|
||||
has_home = 1;
|
||||
// If our bind count is less than 5, we need to actually make use of this data so lets parse it
|
||||
if (bind_count < 5) {
|
||||
pp.binds[4].zone_id = Strings::ToInt(row_b[0]);
|
||||
pp.binds[4].instance_id = Strings::ToInt(row_b[1]);
|
||||
pp.binds[4].x = Strings::ToFloat(row_b[2]);
|
||||
pp.binds[4].y = Strings::ToFloat(row_b[3]);
|
||||
pp.binds[4].z = Strings::ToFloat(row_b[4]);
|
||||
pp.binds[4].heading = Strings::ToFloat(row_b[5]);
|
||||
}
|
||||
// binds
|
||||
int bind_count = 0;
|
||||
for (auto &bind : character_binds) {
|
||||
if (bind.id != e.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (row_b[6] && Strings::ToInt(row_b[6]) == 0)
|
||||
if (bind.slot == 4) {
|
||||
has_home = 1;
|
||||
pp.binds[4].zone_id = bind.zone_id;
|
||||
pp.binds[4].instance_id = bind.instance_id;
|
||||
pp.binds[4].x = bind.x;
|
||||
pp.binds[4].y = bind.y;
|
||||
pp.binds[4].z = bind.z;
|
||||
pp.binds[4].heading = bind.heading;
|
||||
}
|
||||
|
||||
if (bind.slot == 0) {
|
||||
has_bind = 1;
|
||||
}
|
||||
|
||||
bind_count++;
|
||||
}
|
||||
|
||||
if (has_home == 0 || has_bind == 0) {
|
||||
std::string character_list_query = fmt::format(
|
||||
SQL(
|
||||
SELECT
|
||||
`zone_id`, `bind_id`, `x`, `y`, `z`, `heading`
|
||||
FROM
|
||||
`start_zones`
|
||||
WHERE
|
||||
`player_class` = {}
|
||||
AND
|
||||
`player_deity` = {}
|
||||
AND
|
||||
`player_race` = {} {}
|
||||
),
|
||||
p_character_select_entry_struct->Class,
|
||||
p_character_select_entry_struct->Deity,
|
||||
p_character_select_entry_struct->Race,
|
||||
ContentFilterCriteria::apply().c_str()
|
||||
const auto &start_zones = StartZonesRepository::GetWhere(
|
||||
content_db,
|
||||
fmt::format(
|
||||
"`player_class` = {} AND `player_deity` = {} AND `player_race` = {} {}",
|
||||
cse->Class,
|
||||
cse->Deity,
|
||||
cse->Race,
|
||||
ContentFilterCriteria::apply().c_str()
|
||||
)
|
||||
);
|
||||
auto results_bind = content_db.QueryDatabase(character_list_query);
|
||||
for (auto row_d = results_bind.begin(); row_d != results_bind.end(); ++row_d) {
|
||||
/* If a bind_id is specified, make them start there */
|
||||
if (Strings::ToInt(row_d[1]) != 0) {
|
||||
pp.binds[4].zone_id = (uint32) Strings::ToInt(row_d[1]);
|
||||
|
||||
if (!start_zones.empty()) {
|
||||
pp.binds[4].zone_id = start_zones[0].zone_id;
|
||||
pp.binds[4].x = start_zones[0].x;
|
||||
pp.binds[4].y = start_zones[0].y;
|
||||
pp.binds[4].z = start_zones[0].z;
|
||||
pp.binds[4].heading = start_zones[0].heading;
|
||||
|
||||
if (start_zones[0].bind_id != 0) {
|
||||
pp.binds[4].zone_id = start_zones[0].bind_id;
|
||||
auto z = GetZone(pp.binds[4].zone_id);
|
||||
if (z) {
|
||||
pp.binds[4].x = z->safe_x;
|
||||
@@ -238,168 +232,151 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o
|
||||
pp.binds[4].z = z->safe_z;
|
||||
pp.binds[4].heading = z->safe_heading;
|
||||
}
|
||||
}
|
||||
/* Otherwise, use the zone and coordinates given */
|
||||
else {
|
||||
pp.binds[4].zone_id = (uint32) Strings::ToInt(row_d[0]);
|
||||
float x = Strings::ToFloat(row_d[2]);
|
||||
float y = Strings::ToFloat(row_d[3]);
|
||||
float z = Strings::ToFloat(row_d[4]);
|
||||
float heading = Strings::ToFloat(row_d[5]);
|
||||
if (x == 0 && y == 0 && z == 0 && heading == 0) {
|
||||
} else {
|
||||
pp.binds[4].zone_id = start_zones[0].zone_id;
|
||||
pp.binds[4].x = start_zones[0].x;
|
||||
pp.binds[4].y = start_zones[0].y;
|
||||
pp.binds[4].z = start_zones[0].z;
|
||||
pp.binds[4].heading = start_zones[0].heading;
|
||||
|
||||
if (pp.binds[4].x == 0 && pp.binds[4].y == 0 && pp.binds[4].z == 0 && pp.binds[4].heading == 0) {
|
||||
auto zone = GetZone(pp.binds[4].zone_id);
|
||||
if (zone) {
|
||||
x = zone->safe_x;
|
||||
y = zone->safe_y;
|
||||
z = zone->safe_z;
|
||||
heading = zone->safe_heading;
|
||||
pp.binds[4].x = zone->safe_x;
|
||||
pp.binds[4].y = zone->safe_y;
|
||||
pp.binds[4].z = zone->safe_z;
|
||||
pp.binds[4].heading = zone->safe_heading;
|
||||
}
|
||||
}
|
||||
pp.binds[4].x = x;
|
||||
pp.binds[4].y = y;
|
||||
pp.binds[4].z = z;
|
||||
pp.binds[4].heading = heading;
|
||||
}
|
||||
}
|
||||
pp.binds[0] = pp.binds[4];
|
||||
/* If no home bind set, set it */
|
||||
if (has_home == 0) {
|
||||
std::string query = fmt::format(
|
||||
SQL(
|
||||
REPLACE INTO
|
||||
`character_bind`
|
||||
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`)
|
||||
VALUES ({}, {}, {}, {}, {}, {}, {}, {})
|
||||
),
|
||||
character_id,
|
||||
pp.binds[4].zone_id,
|
||||
0,
|
||||
pp.binds[4].x,
|
||||
pp.binds[4].y,
|
||||
pp.binds[4].z,
|
||||
pp.binds[4].heading,
|
||||
4
|
||||
);
|
||||
auto results_bset = QueryDatabase(query);
|
||||
else {
|
||||
LogError("No start zone found for class [{}] deity [{}] race [{}]", cse->Class, cse->Deity, cse->Race);
|
||||
}
|
||||
/* If no regular bind set, set it */
|
||||
|
||||
|
||||
pp.binds[0] = pp.binds[4];
|
||||
|
||||
// If we don't have home set, set it
|
||||
if (has_home == 0) {
|
||||
auto bind = CharacterBindRepository::NewEntity();
|
||||
bind.id = character_id;
|
||||
bind.zone_id = pp.binds[4].zone_id;
|
||||
bind.instance_id = 0;
|
||||
bind.x = pp.binds[4].x;
|
||||
bind.y = pp.binds[4].y;
|
||||
bind.z = pp.binds[4].z;
|
||||
bind.heading = pp.binds[4].heading;
|
||||
bind.slot = 4;
|
||||
CharacterBindRepository::ReplaceOne(*this, bind);
|
||||
}
|
||||
|
||||
// If we don't have regular bind set, set it
|
||||
if (has_bind == 0) {
|
||||
std::string query = fmt::format(
|
||||
SQL(
|
||||
REPLACE INTO
|
||||
`character_bind`
|
||||
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`)
|
||||
VALUES ({}, {}, {}, {}, {}, {}, {}, {})
|
||||
),
|
||||
character_id,
|
||||
pp.binds[0].zone_id,
|
||||
0,
|
||||
pp.binds[0].x,
|
||||
pp.binds[0].y,
|
||||
pp.binds[0].z,
|
||||
pp.binds[0].heading,
|
||||
0
|
||||
);
|
||||
auto results_bset = QueryDatabase(query);
|
||||
auto bind = CharacterBindRepository::NewEntity();
|
||||
bind.id = character_id;
|
||||
bind.zone_id = pp.binds[0].zone_id;
|
||||
bind.instance_id = 0;
|
||||
bind.x = pp.binds[0].x;
|
||||
bind.y = pp.binds[0].y;
|
||||
bind.z = pp.binds[0].z;
|
||||
bind.heading = pp.binds[0].heading;
|
||||
bind.slot = 0;
|
||||
CharacterBindRepository::ReplaceOne(*this, bind);
|
||||
}
|
||||
}
|
||||
/* If our bind count is less than 5, then we have null data that needs to be filled in. */
|
||||
|
||||
// If our bind count is less than 5, then we have null data that needs to be filled in
|
||||
if (bind_count < 5) {
|
||||
// we know that home and main bind must be valid here, so we don't check those
|
||||
// we also use home to fill in the null data like live does.
|
||||
|
||||
std::vector<CharacterBindRepository::CharacterBind> binds;
|
||||
|
||||
for (int i = 1; i < 4; i++) {
|
||||
if (pp.binds[i].zone_id != 0) // we assume 0 is the only invalid one ...
|
||||
if (pp.binds[i].zone_id != 0) { // we assume 0 is the only invalid one ...
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string query = fmt::format(
|
||||
SQL(
|
||||
REPLACE INTO
|
||||
`character_bind`
|
||||
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`)
|
||||
VALUES ({}, {}, {}, {}, {}, {}, {}, {})
|
||||
),
|
||||
character_id,
|
||||
pp.binds[4].zone_id,
|
||||
0,
|
||||
pp.binds[4].x,
|
||||
pp.binds[4].y,
|
||||
pp.binds[4].z,
|
||||
pp.binds[4].heading,
|
||||
i
|
||||
);
|
||||
auto results_bset = QueryDatabase(query);
|
||||
auto bind = CharacterBindRepository::NewEntity();
|
||||
|
||||
bind.slot = i;
|
||||
bind.id = character_id;
|
||||
bind.zone_id = pp.binds[4].zone_id;
|
||||
bind.instance_id = 0;
|
||||
bind.x = pp.binds[4].x;
|
||||
bind.y = pp.binds[4].y;
|
||||
bind.z = pp.binds[4].z;
|
||||
bind.heading = pp.binds[4].heading;
|
||||
binds.emplace_back(bind);
|
||||
}
|
||||
|
||||
CharacterBindRepository::ReplaceMany(*this, binds);
|
||||
}
|
||||
|
||||
character_list_query = fmt::format(
|
||||
SQL(
|
||||
SELECT
|
||||
`slot`, `red`, `green`, `blue`, `use_tint`, `color`
|
||||
FROM
|
||||
`character_material`
|
||||
WHERE
|
||||
`id` = {}
|
||||
),
|
||||
character_id
|
||||
);
|
||||
auto results_b = database.QueryDatabase(character_list_query);
|
||||
uint8 slot = 0;
|
||||
for (auto row_b = results_b.begin(); row_b != results_b.end(); ++row_b) {
|
||||
slot = Strings::ToInt(row_b[0]);
|
||||
pp.item_tint.Slot[slot].Red = Strings::ToInt(row_b[1]);
|
||||
pp.item_tint.Slot[slot].Green = Strings::ToInt(row_b[2]);
|
||||
pp.item_tint.Slot[slot].Blue = Strings::ToInt(row_b[3]);
|
||||
pp.item_tint.Slot[slot].UseTint = Strings::ToInt(row_b[4]);
|
||||
for (auto &cm : character_materials) {
|
||||
pp.item_tint.Slot[cm.slot].Red = cm.red;
|
||||
pp.item_tint.Slot[cm.slot].Green = cm.green;
|
||||
pp.item_tint.Slot[cm.slot].Blue = cm.blue;
|
||||
pp.item_tint.Slot[cm.slot].UseTint = cm.use_tint;
|
||||
pp.item_tint.Slot[cm.slot].Color = cm.color;
|
||||
}
|
||||
|
||||
if (GetCharSelInventory(account_id, p_character_select_entry_struct->Name, &inventory_profile)) {
|
||||
const EQ::ItemData *item = nullptr;
|
||||
const EQ::ItemInstance *inst = nullptr;
|
||||
int16 inventory_slot = 0;
|
||||
if (GetCharSelInventory(inventories, e, &inv)) {
|
||||
const EQ::ItemData *item = nullptr;
|
||||
const EQ::ItemInstance *inst = nullptr;
|
||||
int16 inventory_slot = 0;
|
||||
|
||||
for (uint32 matslot = EQ::textures::textureBegin; matslot < EQ::textures::materialCount; matslot++) {
|
||||
inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(matslot);
|
||||
if (inventory_slot == INVALID_INDEX) { continue; }
|
||||
inst = inventory_profile.GetItem(inventory_slot);
|
||||
if (inst == nullptr)
|
||||
inst = inv.GetItem(inventory_slot);
|
||||
if (inst == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
item = inst->GetItem();
|
||||
if (item == nullptr)
|
||||
if (item == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (matslot > 6) {
|
||||
uint32 item_id_file = 0;
|
||||
// Weapon Models
|
||||
if (inst->GetOrnamentationIDFile() != 0) {
|
||||
item_id_file = inst->GetOrnamentationIDFile();
|
||||
p_character_select_entry_struct->Equip[matslot].Material = item_id_file;
|
||||
} else {
|
||||
cse->Equip[matslot].Material = item_id_file;
|
||||
}
|
||||
else {
|
||||
if (strlen(item->IDFile) > 2) {
|
||||
item_id_file = Strings::ToInt(&item->IDFile[2]);
|
||||
p_character_select_entry_struct->Equip[matslot].Material = item_id_file;
|
||||
cse->Equip[matslot].Material = item_id_file;
|
||||
}
|
||||
}
|
||||
|
||||
if (matslot == EQ::textures::weaponPrimary) {
|
||||
p_character_select_entry_struct->PrimaryIDFile = item_id_file;
|
||||
} else {
|
||||
p_character_select_entry_struct->SecondaryIDFile = item_id_file;
|
||||
cse->PrimaryIDFile = item_id_file;
|
||||
}
|
||||
} else {
|
||||
else {
|
||||
cse->SecondaryIDFile = item_id_file;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Armor Materials/Models
|
||||
uint32 color = (
|
||||
pp.item_tint.Slot[matslot].UseTint ?
|
||||
pp.item_tint.Slot[matslot].Color :
|
||||
inst->GetColor()
|
||||
pp.item_tint.Slot[matslot].Color :
|
||||
inst->GetColor()
|
||||
);
|
||||
p_character_select_entry_struct->Equip[matslot].Material = item->Material;
|
||||
p_character_select_entry_struct->Equip[matslot].EliteModel = item->EliteMaterial;
|
||||
p_character_select_entry_struct->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot);
|
||||
p_character_select_entry_struct->Equip[matslot].Color = color;
|
||||
cse->Equip[matslot].Material = item->Material;
|
||||
cse->Equip[matslot].EliteModel = item->EliteMaterial;
|
||||
cse->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot);
|
||||
cse->Equip[matslot].Color = color;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf("Error loading inventory for %s\n", p_character_select_entry_struct->Name);
|
||||
}
|
||||
else {
|
||||
printf("Error loading inventory for %s\n", cse->Name);
|
||||
}
|
||||
buff_ptr += sizeof(CharacterSelectEntry_Struct);
|
||||
}
|
||||
@@ -849,34 +826,21 @@ bool WorldDatabase::LoadCharacterCreateCombos()
|
||||
return true;
|
||||
}
|
||||
|
||||
// this is a slightly modified version of SharedDatabase::GetInventory(...) for character select use-only
|
||||
bool WorldDatabase::GetCharSelInventory(uint32 account_id, char *name, EQ::InventoryProfile *inv)
|
||||
bool WorldDatabase::GetCharSelInventory(
|
||||
const std::vector<InventoryRepository::Inventory> &inventories,
|
||||
const CharacterDataRepository::CharacterData &character,
|
||||
EQ::InventoryProfile *inv
|
||||
)
|
||||
{
|
||||
if (!account_id || !name || !inv) {
|
||||
if (inventories.empty() || !character.id || !inv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32 character_id = GetCharacterID(name);
|
||||
for (const auto& e : inventories) {
|
||||
if (e.character_id != character.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!character_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& l = InventoryRepository::GetWhere(
|
||||
*this,
|
||||
fmt::format(
|
||||
"`character_id` = {} AND `slot_id` BETWEEN {} AND {}",
|
||||
character_id,
|
||||
EQ::invslot::slotHead,
|
||||
EQ::invslot::slotFeet
|
||||
)
|
||||
);
|
||||
|
||||
if (l.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& e : l) {
|
||||
switch (e.slot_id) {
|
||||
case EQ::invslot::slotFace:
|
||||
case EQ::invslot::slotEar2:
|
||||
|
||||
+7
-1
@@ -20,6 +20,8 @@
|
||||
|
||||
#include "../common/shareddb.h"
|
||||
#include "../common/eq_packet.h"
|
||||
#include "../common/repositories/inventory_repository.h"
|
||||
#include "../common/repositories/character_data_repository.h"
|
||||
|
||||
struct PlayerProfile_Struct;
|
||||
struct CharCreate_Struct;
|
||||
@@ -43,7 +45,11 @@ private:
|
||||
void SetTitaniumDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc);
|
||||
void SetSoFDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc);
|
||||
|
||||
bool GetCharSelInventory(uint32 account_id, char* name, EQ::InventoryProfile* inv);
|
||||
bool GetCharSelInventory(
|
||||
const std::vector<InventoryRepository::Inventory> &inventories,
|
||||
const CharacterDataRepository::CharacterData &character,
|
||||
EQ::InventoryProfile *inv
|
||||
);
|
||||
};
|
||||
|
||||
extern WorldDatabase database;
|
||||
|
||||
+68
-14
@@ -36,11 +36,13 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "shared_task_manager.h"
|
||||
#include "dynamic_zone_manager.h"
|
||||
#include "ucs.h"
|
||||
#include "clientlist.h"
|
||||
|
||||
extern uint32 numzones;
|
||||
extern EQ::Random emu_random;
|
||||
extern WebInterfaceList web_interface;
|
||||
extern SharedTaskManager shared_task_manager;
|
||||
extern ClientList client_list;
|
||||
volatile bool UCSServerAvailable_ = false;
|
||||
void CatchSignal(int sig_num);
|
||||
|
||||
@@ -515,19 +517,27 @@ void ZSList::SendEmoteMessage(const char* to, uint32 to_guilddbid, int16 to_mins
|
||||
SendEmoteMessageRaw(to, to_guilddbid, to_minstatus, type, buffer);
|
||||
}
|
||||
|
||||
void ZSList::SendEmoteMessageRaw(const char* to, uint32 to_guilddbid, int16 to_minstatus, uint32 type, const char* message) {
|
||||
if (!message)
|
||||
void ZSList::SendEmoteMessageRaw(
|
||||
const char *to,
|
||||
uint32 to_guilddbid,
|
||||
int16 to_minstatus,
|
||||
uint32 type,
|
||||
const char *message
|
||||
)
|
||||
{
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
auto pack = new ServerPacket;
|
||||
|
||||
pack->opcode = ServerOP_EmoteMessage;
|
||||
pack->size = sizeof(ServerEmoteMessage_Struct) + strlen(message) + 1;
|
||||
pack->opcode = ServerOP_EmoteMessage;
|
||||
pack->size = sizeof(ServerEmoteMessage_Struct) + strlen(message) + 1;
|
||||
pack->pBuffer = new uchar[pack->size];
|
||||
memset(pack->pBuffer, 0, pack->size);
|
||||
ServerEmoteMessage_Struct* sem = (ServerEmoteMessage_Struct*)pack->pBuffer;
|
||||
ServerEmoteMessage_Struct *sem = (ServerEmoteMessage_Struct *) pack->pBuffer;
|
||||
|
||||
if (to) {
|
||||
strcpy((char *)sem->to, to);
|
||||
strcpy((char *) sem->to, to);
|
||||
}
|
||||
else {
|
||||
sem->to[0] = 0;
|
||||
@@ -535,22 +545,37 @@ void ZSList::SendEmoteMessageRaw(const char* to, uint32 to_guilddbid, int16 to_m
|
||||
|
||||
sem->guilddbid = to_guilddbid;
|
||||
sem->minstatus = to_minstatus;
|
||||
sem->type = type;
|
||||
sem->type = type;
|
||||
strcpy(&sem->message[0], message);
|
||||
char tempto[64] = { 0 };
|
||||
if (to)
|
||||
char tempto[64] = {0};
|
||||
if (to) {
|
||||
strn0cpy(tempto, to, 64);
|
||||
}
|
||||
|
||||
if (tempto[0] == 0) {
|
||||
SendPacket(pack);
|
||||
if (to_guilddbid > 0) {
|
||||
SendPacketToZonesWithGuild(to_guilddbid, pack);
|
||||
}
|
||||
else if (to_minstatus > 0) {
|
||||
SendPacketToZonesWithGMs(pack);
|
||||
} else {
|
||||
SendPacket(pack);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ZoneServer* zs = FindByName(to);
|
||||
|
||||
if (zs != 0)
|
||||
ZoneServer *zs = FindByName(to);
|
||||
if (zs) {
|
||||
zs->SendPacket(pack);
|
||||
else
|
||||
}
|
||||
else if (to_guilddbid > 0) {
|
||||
SendPacketToZonesWithGuild(to_guilddbid, pack);
|
||||
}
|
||||
else if (to_minstatus > 0) {
|
||||
SendPacketToZonesWithGMs(pack);
|
||||
}
|
||||
else {
|
||||
SendPacket(pack);
|
||||
}
|
||||
}
|
||||
delete pack;
|
||||
}
|
||||
@@ -871,6 +896,33 @@ bool ZSList::SendPacketToBootedZones(ServerPacket* pack)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ZSList::SendPacketToZonesWithGuild(uint32 guild_id, ServerPacket* pack)
|
||||
{
|
||||
auto servers = client_list.GetGuildZoneServers(guild_id);
|
||||
for (auto const& z : zone_server_list) {
|
||||
for (auto const& server_id : servers) {
|
||||
if (z->GetID() == server_id && z->GetZoneID() > 0) {
|
||||
z->SendPacket(pack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ZSList::SendPacketToZonesWithGMs(ServerPacket* pack)
|
||||
{
|
||||
for (auto const &z: zone_server_list) {
|
||||
for (auto const &server_id: client_list.GetZoneServersWithGMs()) {
|
||||
if (z->GetID() == server_id && z->GetZoneID() > 0) {
|
||||
z->SendPacket(pack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
|
||||
{
|
||||
static auto pack = ServerPacket(ServerOP_ServerReloadRequest, sizeof(ServerReload::Request));
|
||||
@@ -894,6 +946,8 @@ void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
|
||||
ServerReload::Type::Commands,
|
||||
ServerReload::Type::PerlExportSettings,
|
||||
ServerReload::Type::DataBucketsCache,
|
||||
ServerReload::Type::Quests,
|
||||
ServerReload::Type::QuestsTimerReset,
|
||||
ServerReload::Type::WorldRepop,
|
||||
ServerReload::Type::WorldWithRespawn
|
||||
};
|
||||
|
||||
@@ -29,6 +29,8 @@ public:
|
||||
bool SendPacket(ServerPacket *pack);
|
||||
bool SendPacket(uint32 zoneid, ServerPacket *pack);
|
||||
bool SendPacket(uint32 zoneid, uint16 instanceid, ServerPacket *pack);
|
||||
bool SendPacketToZonesWithGuild(uint32 guild_id, ServerPacket *pack);
|
||||
bool SendPacketToZonesWithGMs(ServerPacket *pack);
|
||||
bool SendPacketToBootedZones(ServerPacket* pack);
|
||||
bool SetLockedZone(uint16 iZoneID, bool iLock);
|
||||
|
||||
|
||||
+16
-8
@@ -571,7 +571,13 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
);
|
||||
}
|
||||
}
|
||||
zoneserver_list.SendPacket(pack);
|
||||
if (scm->guilddbid > 0) {
|
||||
zoneserver_list.SendPacketToZonesWithGuild(scm->guilddbid, pack);
|
||||
} else if (scm->chan_num == ChatChannel_GMSAY) {
|
||||
zoneserver_list.SendPacketToZonesWithGMs(pack);
|
||||
} else {
|
||||
zoneserver_list.SendPacket(pack);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -729,13 +735,15 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
zs = zoneserver_list.FindByID(s->zone_server_id);
|
||||
} else if (s->zone_id) {
|
||||
zs = zoneserver_list.FindByName(ZoneName(s->zone_id));
|
||||
} else if (s->instance_id) {
|
||||
zs = zoneserver_list.FindByInstanceID(s->instance_id);
|
||||
} else {
|
||||
zoneserver_list.SendEmoteMessage(
|
||||
s->admin_name,
|
||||
0,
|
||||
AccountStatus::Player,
|
||||
Chat::White,
|
||||
"Error: SOP_ZoneShutdown: neither ID nor name specified"
|
||||
"Error: SOP_ZoneShutdown: Zone ID, Instance ID, nor Zone Short Name specified"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1510,7 +1518,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
|
||||
guild->tribute.timer.Disable();
|
||||
|
||||
zoneserver_list.SendPacketToBootedZones(pack);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(data->guild_id, pack);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1553,7 +1561,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
guild_mgr.UpdateDbGuildFavor(data->guild_id, data->favor);
|
||||
guild_mgr.UpdateDbTributeTimeRemaining(data->guild_id, data->time_remaining);
|
||||
|
||||
zoneserver_list.SendPacketToBootedZones(pack);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(data->guild_id, pack);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1587,7 +1595,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
data->time_remaining = in->time_remaining;
|
||||
strn0cpy(data->player_name, in->player_name, sizeof(data->player_name));
|
||||
|
||||
zoneserver_list.SendPacketToBootedZones(out);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, out);
|
||||
safe_delete(out);
|
||||
}
|
||||
break;
|
||||
@@ -1610,7 +1618,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
out->tribute_id_2_tier = guild->tribute.id_2_tier;
|
||||
out->time_remaining = guild_mgr.GetGuildTributeTimeRemaining(in->guild_id);
|
||||
|
||||
zoneserver_list.SendPacketToBootedZones(sp);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, sp);
|
||||
safe_delete(sp);
|
||||
}
|
||||
|
||||
@@ -1630,7 +1638,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
out->tribute_timer = guild_mgr.GetGuildTributeTimeRemaining(in->guild_id);
|
||||
out->trophy_timer = 0;
|
||||
|
||||
zoneserver_list.SendPacketToBootedZones(sp);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, sp);
|
||||
safe_delete(sp);
|
||||
}
|
||||
|
||||
@@ -1654,7 +1662,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
out->member_time = in->member_time;
|
||||
strn0cpy(out->player_name, in->player_name, sizeof(out->player_name));
|
||||
|
||||
zoneserver_list.SendPacketToBootedZones(sp);
|
||||
zoneserver_list.SendPacketToZonesWithGuild(out->guild_id, sp);
|
||||
safe_delete(sp)
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -53,6 +53,8 @@ public:
|
||||
|
||||
inline const char* GetZoneName() const { return zone_name; }
|
||||
inline const char* GetZoneLongName() const { return long_name; }
|
||||
inline std::string GetCurrentVersion() const { return CURRENT_VERSION; }
|
||||
inline std::string GetCompileDate() const { return COMPILE_DATE; }
|
||||
const char* GetCompileTime() const{ return compiled; }
|
||||
void SetCompile(char* in_compile){ strcpy(compiled,in_compile); }
|
||||
inline uint32 GetZoneID() const { return zone_server_zone_id; }
|
||||
|
||||
@@ -173,6 +173,7 @@ SET(zone_sources
|
||||
zone_event_scheduler.cpp
|
||||
zone_npc_factions.cpp
|
||||
zone_reload.cpp
|
||||
zone_save_state.cpp
|
||||
zoning.cpp
|
||||
)
|
||||
|
||||
@@ -292,6 +293,7 @@ SET(zone_headers
|
||||
zonedump.h
|
||||
zone_cli.h
|
||||
zone_reload.h
|
||||
zone_save_state.h
|
||||
zone_cli.cpp)
|
||||
|
||||
ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers})
|
||||
|
||||
+13
-1
@@ -2507,6 +2507,12 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_resumed_from_zone_suspend && !IsQueuedForCorpse()) {
|
||||
LogInfo("NPC [{}] is resumed from zone suspend, cannot kill until zone resume is complete.", GetCleanName());
|
||||
SetHP(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsMultiQuestEnabled()) {
|
||||
for (auto &i: m_hand_in.items) {
|
||||
if (i.is_multiquest_item && i.item->GetItem()->NoDrop != 0) {
|
||||
@@ -2627,7 +2633,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
|
||||
bool pet_owner_is_client = give_exp->IsPet() && owner->IsClient();
|
||||
bool pet_owner_is_bot = give_exp->IsPet() && owner->IsBot();
|
||||
bool owner_is_client = owner->IsClient();
|
||||
|
||||
|
||||
bool is_in_same_group_or_raid = (
|
||||
pet_owner_is_client ||
|
||||
(pet_owner_is_bot && owner->IsInGroupOrRaid(ulimate_owner)) ||
|
||||
@@ -3041,9 +3047,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;
|
||||
|
||||
|
||||
+356
-156
@@ -2287,7 +2287,7 @@ void Bot::AI_Process()
|
||||
// COMBAT RANGE CALCS
|
||||
bool front_mob = InFrontMob(tar, GetX(), GetY());
|
||||
bool behind_mob = BehindMob(tar, GetX(), GetY());
|
||||
uint8 stop_melee_level = GetStopMeleeLevel();
|
||||
bool stop_melee_level = GetLevel() >= GetStopMeleeLevel();
|
||||
tar_distance = sqrt(tar_distance); // sqrt this for future calculations
|
||||
// Item variables
|
||||
const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary);
|
||||
@@ -2314,7 +2314,7 @@ void Bot::AI_Process()
|
||||
if (PULLING_BOT || RETURNING_BOT) {
|
||||
if (!TargetValidation(tar)) { return; }
|
||||
|
||||
if (!DoLosChecks(tar)) {
|
||||
if (RuleB(Bots, BotsRequireLoS) && !HasLoS()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2442,7 +2442,7 @@ void Bot::AI_Process()
|
||||
|
||||
ranged_timer.Start();
|
||||
}
|
||||
else if (!IsBotRanged() && GetLevel() < stop_melee_level) {
|
||||
else if (!IsBotRanged() && !stop_melee_level) {
|
||||
if (
|
||||
IsTaunting() ||
|
||||
!GetMaxMeleeRange() ||
|
||||
@@ -3128,7 +3128,6 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
|
||||
bool is_two_hander = input.p_item && input.p_item->GetItem()->IsType2HWeapon();
|
||||
bool is_shield = input.s_item && input.s_item->GetItem()->IsTypeShield();
|
||||
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage();
|
||||
bool is_stop_melee_level = GetLevel() >= input.stop_melee_level;
|
||||
|
||||
if (IsTaunting()) { // Taunting bots
|
||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerTauntingMeleeDistanceMultiplier);
|
||||
@@ -3143,7 +3142,7 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
|
||||
o.melee_distance_min = std::max(min_distance, (desired_range / 2));
|
||||
o.melee_distance = std::min(max_distance, desired_range);
|
||||
}
|
||||
else if (is_stop_melee_level) { // Casters
|
||||
else if (input.stop_melee_level) { // Casters
|
||||
float desired_range = GetBotDistanceRanged();
|
||||
|
||||
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range / 2));
|
||||
@@ -3176,14 +3175,17 @@ bool Bot::IsValidTarget(
|
||||
return false;
|
||||
}
|
||||
|
||||
SetHasLoS(DoLosChecks(tar));
|
||||
|
||||
bool invalid_target_state = false;
|
||||
|
||||
if (HOLDING ||
|
||||
!tar->IsNPC() ||
|
||||
(tar->IsMezzed() && !HasBotAttackFlag(tar)) ||
|
||||
(!Charmed() && tar->GetUltimateOwner()->IsOfClientBotMerc()) ||
|
||||
lo_distance > leash_distance ||
|
||||
tar_distance > leash_distance ||
|
||||
(!GetAttackingFlag() && !CheckLosCheat(tar) && !leash_owner->CheckLosCheat(tar)) ||
|
||||
(!GetAttackingFlag() && !HasLoS()) ||
|
||||
!IsAttackAllowed(tar)
|
||||
) {
|
||||
invalid_target_state = true;
|
||||
@@ -3578,8 +3580,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 +3712,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);
|
||||
}
|
||||
@@ -9531,7 +9536,14 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool precheck
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!BotHasEnoughMana(spell_id)) {
|
||||
if (
|
||||
!BotHasEnoughMana(spell_id) &&
|
||||
(
|
||||
!RuleB(Bots, FinishBuffing) ||
|
||||
IsEngaged() ||
|
||||
IsBotBuffSpellType(spell_type)
|
||||
)
|
||||
) {
|
||||
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !BotHasEnoughMana.'", GetCleanName(), GetSpellName(spell_id));
|
||||
return false;
|
||||
}
|
||||
@@ -9790,6 +9802,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 +9826,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 +9856,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() ||
|
||||
(
|
||||
@@ -10586,6 +10599,8 @@ BotSpell Bot::GetSpellByHealType(uint16 spell_type, Mob* tar) {
|
||||
return GetBestBotSpellForHealOverTime(this, tar, spell_type);
|
||||
case BotSpellTypes::GroupHoTHeals:
|
||||
return GetBestBotSpellForGroupHealOverTime(this, tar, spell_type);
|
||||
default:
|
||||
return BotSpell(); // Return an empty BotSpell if no valid spell type is found
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10637,7 +10652,7 @@ int Bot::GetDefaultSetting(uint16 setting_category, uint16 setting_type, uint8 s
|
||||
case BotSettingCategories::SpellTypeAnnounceCast:
|
||||
return GetDefaultSpellTypeAnnounceCast(setting_type, stance);
|
||||
default:
|
||||
break;
|
||||
return 0; // Default return value for unrecognized categories
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10676,7 +10691,7 @@ int Bot::GetSetting(uint16 setting_category, uint16 setting_type) {
|
||||
case BotSettingCategories::SpellTypeAnnounceCast:
|
||||
return GetSpellTypeAnnounceCast(setting_type);
|
||||
default:
|
||||
break;
|
||||
return 0; // Default return value for unrecognized categories
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11403,7 +11418,7 @@ bool Bot::AttemptForcedCastSpell(Mob* tar, uint16 spell_id, bool is_disc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DoLosChecks(tar)) {
|
||||
if (!HasLoS() && !DoLosChecks(tar)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -11566,18 +11581,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 +11849,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 +11857,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 +11892,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 +11914,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 +11937,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;
|
||||
}
|
||||
@@ -11806,74 +12031,53 @@ void Bot::DoCombatPositioning(
|
||||
bool behind_mob,
|
||||
bool front_mob
|
||||
) {
|
||||
if (HasTargetReflection()) {
|
||||
if (tar->IsRooted() && !IsTaunting()) { // Move non-taunters out of range
|
||||
if (tar_distance <= melee_distance_max) {
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 1.25f), GetBehindMob(), false)) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
if (!tar->IsFeared()) {
|
||||
bool is_too_close = tar_distance < melee_distance_min;
|
||||
bool los_adjust = !HasRequiredLoSForPositioning(tar);
|
||||
|
||||
if (tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range
|
||||
bool rooted_adjust = tar_distance <= melee_distance_max && HasTargetReflection();
|
||||
|
||||
if (rooted_adjust) {
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 1.25f), GetBehindMob(), !GetBehindMob())) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (
|
||||
tar_distance < melee_distance_min ||
|
||||
(!front_mob && IsTaunting())
|
||||
) { // Back up any bots that are too close or if they're taunting and not in front of the mob
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!tar->IsFeared()) {
|
||||
if (
|
||||
tar_distance < melee_distance_min ||
|
||||
(GetBehindMob() && !behind_mob) ||
|
||||
(IsTaunting() && !front_mob) ||
|
||||
!HasRequiredLoSForPositioning(tar)
|
||||
) { // Regular adjustment
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (
|
||||
tar->IsEnraged() &&
|
||||
!IsTaunting() &&
|
||||
!stop_melee_level &&
|
||||
!behind_mob
|
||||
) { // Move non-taunting melee bots behind target during enrage
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
if (IsTaunting()) { // Taunting adjustments
|
||||
Mob* mob_tar = tar->GetTarget();
|
||||
bool taunting_adjust = (!front_mob || is_too_close || los_adjust);
|
||||
|
||||
if (mob_tar) {
|
||||
if (
|
||||
RuleB(Bots, TauntingBotsFollowTopHate) &&
|
||||
(Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate))
|
||||
) { // If enabled, taunting bots will stick to top hate
|
||||
Goal = mob_tar->GetPosition();
|
||||
if (taunting_adjust) {
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, true)) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
return;
|
||||
}
|
||||
else { // Otherwise, stick to any other bots that are taunting
|
||||
if (
|
||||
mob_tar->IsBot() &&
|
||||
mob_tar->CastToBot()->IsTaunting() &&
|
||||
(Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate))
|
||||
) {
|
||||
Goal = mob_tar->GetPosition();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tar->IsEnraged() && !stop_melee_level && !IsBotRanged()) { // Move non-taunting melee bots behind target during enrage
|
||||
bool enraged_adjust = !behind_mob || is_too_close || los_adjust;
|
||||
|
||||
if (enraged_adjust) {
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Regular adjustments
|
||||
bool regular_adjust =
|
||||
is_too_close ||
|
||||
los_adjust ||
|
||||
(!GetBehindMob() && !front_mob) ||
|
||||
(GetBehindMob() && !behind_mob);
|
||||
|
||||
if (regular_adjust) {
|
||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), !GetBehindMob())) {
|
||||
RunToGoalWithJitter(Goal);
|
||||
|
||||
return;
|
||||
@@ -11885,8 +12089,6 @@ void Bot::DoCombatPositioning(
|
||||
}
|
||||
|
||||
DoFaceCheckNoJitter(tar);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool Bot::PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behind_only, bool front_only, bool bypass_los) {
|
||||
@@ -11983,7 +12185,11 @@ bool Bot::RequiresLoSForPositioning() {
|
||||
}
|
||||
|
||||
for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) {
|
||||
if (IsBotSpellTypeDetrimental(i) && !GetSpellTypeHold(i)) {
|
||||
if (IsHealBotSpellType(i) || i == BotSpellTypes::PBAENuke) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!GetSpellTypeHold(i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -11996,7 +12202,7 @@ bool Bot::HasRequiredLoSForPositioning(Mob* tar) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (RequiresLoSForPositioning() && !DoLosChecks(tar)) {
|
||||
if (RequiresLoSForPositioning() && !HasLoS()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -12011,10 +12217,6 @@ bool Bot::HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob*
|
||||
for (auto& close_mob : caster->m_close_mobs) {
|
||||
Mob* m = close_mob.second;
|
||||
|
||||
if (tar == m) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (spell_type) {
|
||||
case BotSpellTypes::AELull:
|
||||
if (m->GetSpecialAbility(SpecialAbility::PacifyImmunity)) {
|
||||
@@ -12106,8 +12308,6 @@ bool Bot::HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob*
|
||||
return false;
|
||||
}
|
||||
|
||||
SetHasLoS(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -235,7 +235,7 @@ static std::map<uint16, std::string> botSubType_names = {
|
||||
struct CombatRangeInput {
|
||||
Mob* target;
|
||||
float target_distance;
|
||||
uint8 stop_melee_level;
|
||||
bool stop_melee_level;
|
||||
const EQ::ItemInstance* p_item;
|
||||
const EQ::ItemInstance* s_item;
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ void bot_command_attack(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c->DoLosChecks(target_mob)) {
|
||||
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) {
|
||||
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -525,6 +525,9 @@ void bot_command_cast(Client* c, const Seperator* sep)
|
||||
continue;
|
||||
}
|
||||
|
||||
bool requires_los = !(IsAnyHealSpell(spell_id) && !IsPBAESpell(spell_id));
|
||||
bot_iter->SetHasLoS(requires_los ? bot_iter->DoLosChecks(new_tar) : true);
|
||||
|
||||
if (!bot_iter->AttemptAACastSpell(tar, spell_id, rank)) {
|
||||
continue;
|
||||
}
|
||||
@@ -543,6 +546,9 @@ void bot_command_cast(Client* c, const Seperator* sep)
|
||||
tar = bot_iter;
|
||||
}
|
||||
|
||||
bool los_required = bot_iter != tar && !IsAnyHealSpell(chosen_spell_id) && !IsPBAESpell(chosen_spell_id);
|
||||
bot_iter->SetHasLoS(los_required ? bot_iter->DoLosChecks(new_tar) : true);
|
||||
|
||||
if (bot_iter->AttemptForcedCastSpell(tar, chosen_spell_id)) {
|
||||
if (!first_found) {
|
||||
first_found = bot_iter;
|
||||
@@ -556,7 +562,8 @@ void bot_command_cast(Client* c, const Seperator* sep)
|
||||
}
|
||||
else {
|
||||
bot_iter->SetCommandedSpell(true);
|
||||
|
||||
bot_iter->SetHasLoS(BotSpellTypeRequiresLoS(spell_type) ? bot_iter->DoLosChecks(new_tar) : true);
|
||||
|
||||
if (bot_iter->AICastSpell(new_tar, 100, spell_type, sub_target_type, sub_type)) {
|
||||
if (!first_found) {
|
||||
first_found = bot_iter;
|
||||
|
||||
@@ -197,7 +197,11 @@ void bot_command_depart(Client* c, const Seperator* sep)
|
||||
|
||||
bot_iter->SetCommandedSpell(true);
|
||||
|
||||
if (!IsValidSpellAndLoS(itr->SpellId, bot_iter->HasLoS())) {
|
||||
if (!IsValidSpell(itr->SpellId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BotRequiresLoSToCast(BotSpellTypes::Teleport, itr->SpellId) && !bot_iter->HasLoS()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ void bot_command_precombat(Client* c, const Seperator* sep)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c->DoLosChecks(c->GetTarget())) {
|
||||
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(c->GetTarget())) {
|
||||
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
|
||||
|
||||
return;
|
||||
|
||||
@@ -48,7 +48,7 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c->DoLosChecks(target_mob)) {
|
||||
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) {
|
||||
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
|
||||
|
||||
return;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
+89
-200
@@ -41,7 +41,10 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chance < 100 && zone->random.Int(0, 100) > chance) {
|
||||
if (
|
||||
!IsCommandedSpell() &&
|
||||
zone->random.Int(0, 100) > chance
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -61,10 +64,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
|
||||
bot_spell.SpellIndex = 0;
|
||||
bot_spell.ManaCost = 0;
|
||||
|
||||
if (BotSpellTypeRequiresLoS(spell_type) && tar != this) {
|
||||
SetHasLoS(DoLosChecks(tar));
|
||||
}
|
||||
else {
|
||||
if (!BotSpellTypeRequiresLoS(spell_type) || tar == this) {
|
||||
SetHasLoS(true);
|
||||
}
|
||||
|
||||
@@ -218,8 +218,11 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
|
||||
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, (IsAEBotSpellType(spell_type) || sub_target_type == CommandedSubTypes::AETarget), sub_target_type, sub_type);
|
||||
|
||||
for (const auto& s : bot_spell_list) {
|
||||
if (!IsValidSpell(s.SpellId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
|
||||
if (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -273,7 +276,11 @@ bool Bot::BotCastMez(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
|
||||
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type));
|
||||
|
||||
for (const auto& s : bot_spell_list) {
|
||||
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
|
||||
if (!IsValidSpell(s.SpellId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -281,7 +288,7 @@ bool Bot::BotCastMez(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
|
||||
Mob* add_mob = GetFirstIncomingMobToMez(this, s.SpellId, spell_type, IsAEBotSpellType(spell_type));
|
||||
|
||||
if (!add_mob) {
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
tar = add_mob;
|
||||
@@ -327,7 +334,11 @@ bool Bot::BotCastCure(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spe
|
||||
|
||||
bot_spell = GetBestBotSpellForCure(this, tar, spell_type);
|
||||
|
||||
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
|
||||
if (!IsValidSpell(bot_spell.SpellId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -397,7 +408,11 @@ bool Bot::BotCastPet(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
|
||||
bot_spell = GetFirstBotSpellBySpellType(this, spell_type);
|
||||
}
|
||||
|
||||
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
|
||||
if (!IsValidSpell(bot_spell.SpellId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -418,6 +433,10 @@ bool Bot::BotCastPet(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
|
||||
}
|
||||
|
||||
bool Bot::BotCastNuke(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type) {
|
||||
if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spell_type == BotSpellTypes::Stun || spell_type == BotSpellTypes::AEStun) {
|
||||
uint8 stun_chance = (tar->IsCasting() ? RuleI(Bots, StunCastChanceIfCasting) : RuleI(Bots, StunCastChanceNormal));
|
||||
|
||||
@@ -434,25 +453,25 @@ bool Bot::BotCastNuke(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spe
|
||||
) {
|
||||
bot_spell = GetBestBotSpellForStunByTargetType(this, ST_TargetOptional, spell_type, IsAEBotSpellType(spell_type), tar);
|
||||
}
|
||||
|
||||
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
|
||||
|
||||
if (!IsValidSpell(bot_spell.SpellId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
|
||||
if (!IsValidSpell(bot_spell.SpellId)) {
|
||||
bot_spell = GetBestBotSpellForNukeByBodyType(this, tar->GetBodyType(), spell_type, IsAEBotSpellType(spell_type), tar);
|
||||
}
|
||||
|
||||
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS()) && spell_type == BotSpellTypes::Nuke && bot_class == Class::Wizard) {
|
||||
if (spell_type == BotSpellTypes::Nuke && bot_class == Class::Wizard && !IsValidSpell(bot_spell.SpellId)) {
|
||||
bot_spell = GetBestBotWizardNukeSpellByTargetResists(this, tar, spell_type);
|
||||
}
|
||||
|
||||
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
|
||||
if (!IsValidSpell(bot_spell.SpellId)) {
|
||||
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type));
|
||||
|
||||
for (const auto& s : bot_spell_list) {
|
||||
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
|
||||
if (!IsValidSpell(s.SpellId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -562,7 +581,9 @@ bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
|
||||
// Allow bots to cast buff spells even if they are out of mana
|
||||
if (
|
||||
RuleB(Bots, FinishBuffing) &&
|
||||
manaCost > hasMana && AIBot_spells[i].type == BotSpellTypes::Buff
|
||||
manaCost > hasMana &&
|
||||
!IsEngaged() &&
|
||||
IsBotBuffSpellType(AIBot_spells[i].type)
|
||||
) {
|
||||
SetMana(manaCost);
|
||||
}
|
||||
@@ -908,7 +929,11 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffect(Bot* caster, uint16 spell_ty
|
||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||
|
||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
||||
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -946,7 +971,11 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* caster, ui
|
||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||
|
||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
||||
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -987,7 +1016,11 @@ std::list<BotSpell> Bot::GetBotSpellsBySpellType(Bot* caster, uint16 spell_type)
|
||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||
|
||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
||||
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1016,7 +1049,11 @@ std::vector<BotSpell_wPriority> Bot::GetPrioritizedBotSpellsBySpellType(Bot* cas
|
||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||
|
||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
||||
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1103,7 +1140,11 @@ BotSpell Bot::GetFirstBotSpellBySpellType(Bot* caster, uint16 spell_type) {
|
||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||
|
||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
||||
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1135,7 +1176,6 @@ BotSpell Bot::GetBestBotSpellForVeryFastHeal(Bot* caster, Mob* tar, uint16 spell
|
||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
|
||||
|
||||
for (auto bot_spell_list_itr : bot_spell_list) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (
|
||||
IsVeryFastHealSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) {
|
||||
result.SpellId = bot_spell_list_itr.SpellId;
|
||||
@@ -1161,7 +1201,6 @@ BotSpell Bot::GetBestBotSpellForFastHeal(Bot* caster, Mob* tar, uint16 spell_typ
|
||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
|
||||
|
||||
for (auto bot_spell_list_itr : bot_spell_list) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (IsFastHealSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) {
|
||||
result.SpellId = bot_spell_list_itr.SpellId;
|
||||
result.SpellIndex = bot_spell_list_itr.SpellIndex;
|
||||
@@ -1186,7 +1225,6 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* caster, Mob* tar, uint16 spell
|
||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_HealOverTime);
|
||||
|
||||
for (auto bot_spell_list_itr : bot_spell_list) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (IsHealOverTimeSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) {
|
||||
result.SpellId = bot_spell_list_itr.SpellId;
|
||||
result.SpellIndex = bot_spell_list_itr.SpellIndex;
|
||||
@@ -1243,7 +1281,6 @@ BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* caster, Mob* tar, u
|
||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
|
||||
|
||||
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (IsRegularSingleTargetHealSpell(bot_spell_list_itr->SpellId) && caster->CastChecks(bot_spell_list_itr->SpellId, tar, spell_type)) {
|
||||
result.SpellId = bot_spell_list_itr->SpellId;
|
||||
result.SpellIndex = bot_spell_list_itr->SpellIndex;
|
||||
@@ -1268,7 +1305,6 @@ BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* caster, Mob* tar, uint16
|
||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
|
||||
|
||||
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (IsRegularSingleTargetHealSpell(bot_spell_list_itr->SpellId) && caster->CastChecks(bot_spell_list_itr->SpellId, tar, spell_type)) {
|
||||
result.SpellId = bot_spell_list_itr->SpellId;
|
||||
result.SpellIndex = bot_spell_list_itr->SpellIndex;
|
||||
@@ -1298,7 +1334,6 @@ BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* caster, Mob* tar, uint16 spell_ty
|
||||
int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type);
|
||||
|
||||
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (IsRegularGroupHealSpell(bot_spell_list_itr->SpellId)) {
|
||||
uint16 spell_id = bot_spell_list_itr->SpellId;
|
||||
|
||||
@@ -1337,7 +1372,6 @@ BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* caster, Mob* tar, uint16
|
||||
int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type);
|
||||
|
||||
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (IsGroupHealOverTimeSpell(bot_spell_list_itr->SpellId)) {
|
||||
uint16 spell_id = bot_spell_list_itr->SpellId;
|
||||
|
||||
@@ -1376,7 +1410,6 @@ BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* caster, Mob* tar, uint16
|
||||
int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type);
|
||||
|
||||
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (IsGroupCompleteHealSpell(bot_spell_list_itr->SpellId)) {
|
||||
uint16 spell_id = bot_spell_list_itr->SpellId;
|
||||
|
||||
@@ -1410,7 +1443,6 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* caster, uint16 spell_type) {
|
||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Mez);
|
||||
|
||||
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (
|
||||
IsMesmerizeSpell(bot_spell_list_itr->SpellId) &&
|
||||
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)
|
||||
@@ -1433,11 +1465,10 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
|
||||
if (caster && caster->GetOwner()) {
|
||||
int spell_range = caster->GetActSpellRange(spell_id, spells[spell_id].range);
|
||||
int spell_ae_range = caster->GetAOERange(spell_id);
|
||||
int buff_count = 0;
|
||||
bool is_pbae_spell = IsPBAESpell(spell_id);
|
||||
NPC* npc = nullptr;
|
||||
|
||||
for (auto& close_mob : caster->m_close_mobs) {
|
||||
buff_count = 0;
|
||||
npc = close_mob.second->CastToNPC();
|
||||
|
||||
if (!npc) {
|
||||
@@ -1448,29 +1479,29 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_pbae_spell) {
|
||||
if (spell_ae_range < Distance(caster->GetPosition(), npc->GetPosition())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (spell_range < Distance(caster->GetPosition(), npc->GetPosition())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (AE) {
|
||||
int target_count = 0;
|
||||
|
||||
for (auto& close_mob : caster->m_close_mobs) {
|
||||
Mob* m = close_mob.second;
|
||||
|
||||
if (npc == m) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!caster->IsValidMezTarget(caster->GetOwner(), m, spell_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsPBAESpell(spell_id)) {
|
||||
if (spell_ae_range < Distance(caster->GetPosition(), m->GetPosition())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (spell_range < Distance(m->GetPosition(), npc->GetPosition())) {
|
||||
continue;
|
||||
}
|
||||
if (spell_ae_range < Distance(npc->GetPosition(), m->GetPosition())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (caster->CastChecks(spell_id, m, spell_type, true, true)) {
|
||||
@@ -1486,11 +1517,6 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
|
||||
continue;
|
||||
}
|
||||
|
||||
if (zone->random.Int(1, 100) < RuleI(Bots, AEMezChance)) {
|
||||
caster->SetSpellTypeRecastTimer(spell_type, RuleI(Bots, MezFailDelay));
|
||||
return result;
|
||||
}
|
||||
|
||||
result = npc;
|
||||
}
|
||||
else {
|
||||
@@ -1502,18 +1528,10 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
|
||||
continue;
|
||||
}
|
||||
|
||||
if (zone->random.Int(1, 100) < RuleI(Bots, MezChance)) {
|
||||
caster->SetSpellTypeRecastTimer(spell_type, RuleI(Bots, MezAEFailDelay));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result = npc;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
caster->SetHasLoS(true);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1534,7 +1552,6 @@ BotSpell Bot::GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type) {
|
||||
std::string pet_type = GetBotMagicianPetType(caster);
|
||||
|
||||
for(std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (
|
||||
IsSummonPetSpell(bot_spell_list_itr->SpellId) &&
|
||||
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId) &&
|
||||
@@ -1716,7 +1733,6 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType ta
|
||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffectAndTargetType(caster, spell_type, SE_CurrentHP, target_type);
|
||||
|
||||
for(std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (IsPureNukeSpell(bot_spell_list_itr->SpellId) || IsDamageSpell(bot_spell_list_itr->SpellId)) {
|
||||
if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) {
|
||||
continue;
|
||||
@@ -1729,7 +1745,6 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType ta
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
caster->IsCommandedSpell() ||
|
||||
!AE ||
|
||||
@@ -1766,7 +1781,6 @@ BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* caster, SpellTargetType ta
|
||||
|
||||
for(std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr)
|
||||
{
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (IsStunSpell(bot_spell_list_itr->SpellId)) {
|
||||
if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) {
|
||||
continue;
|
||||
@@ -1830,7 +1844,6 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target,
|
||||
bool spell_selected = false;
|
||||
|
||||
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (!caster->IsValidSpellRange(bot_spell_list_itr->SpellId, target)) {
|
||||
continue;
|
||||
}
|
||||
@@ -1887,8 +1900,6 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target,
|
||||
|
||||
if (!spell_selected) {
|
||||
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
|
||||
if (caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)) {
|
||||
if (caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)) {
|
||||
spell_selected = true;
|
||||
@@ -1926,7 +1937,11 @@ BotSpell Bot::GetDebuffBotSpell(Bot* caster, Mob *tar, uint16 spell_type) {
|
||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||
|
||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
||||
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1972,7 +1987,11 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* caster, Mob *tar, uint16 spell
|
||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||
|
||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
||||
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2091,7 +2110,6 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spell_type)
|
||||
case BotSpellTypes::AERains:
|
||||
case BotSpellTypes::AEStun:
|
||||
case BotSpellTypes::AESnare:
|
||||
case BotSpellTypes::AEMez:
|
||||
case BotSpellTypes::AESlow:
|
||||
case BotSpellTypes::AEDebuff:
|
||||
case BotSpellTypes::AEFear:
|
||||
@@ -2157,6 +2175,8 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spell_type)
|
||||
case BotSpellTypes::PetVeryFastHeals:
|
||||
case BotSpellTypes::PetHoTHeals:
|
||||
return RuleI(Bots, PercentChanceToCastHeal);
|
||||
case BotSpellTypes::AEMez:
|
||||
return RuleI(Bots, PercentChanceToCastAEMez);
|
||||
default:
|
||||
return RuleI(Bots, PercentChanceToCastOtherType);
|
||||
}
|
||||
@@ -2821,7 +2841,6 @@ BotSpell Bot::GetBestBotSpellForRez(Bot* caster, Mob* target, uint16 spell_type)
|
||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Revive);
|
||||
|
||||
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (
|
||||
IsResurrectSpell(bot_spell_list_itr->SpellId) &&
|
||||
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)
|
||||
@@ -2849,7 +2868,6 @@ BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_typ
|
||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Charm);
|
||||
|
||||
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
|
||||
// Assuming all the spells have been loaded into this list by level and in descending order
|
||||
if (
|
||||
IsCharmSpell(bot_spell_list_itr->SpellId) &&
|
||||
caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)
|
||||
@@ -2865,132 +2883,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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "../../zone.h"
|
||||
|
||||
inline void RunTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
||||
{
|
||||
if (expected == actual) {
|
||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||
} else {
|
||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||
std::cerr << " 📌 Expected: " << expected << "\n";
|
||||
std::cerr << " ❌ Got: " << actual << "\n";
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
inline void RunTest(const std::string &test_name, bool expected, bool actual)
|
||||
{
|
||||
if (expected == actual) {
|
||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||
}
|
||||
else {
|
||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||
std::cerr << " 📌 Expected: " << (expected ? "true" : "false") << "\n";
|
||||
std::cerr << " ❌ Got: " << (actual ? "true" : "false") << "\n";
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
inline void RunTest(const std::string &test_name, int expected, int actual)
|
||||
{
|
||||
if (expected == actual) {
|
||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||
}
|
||||
else {
|
||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||
std::cerr << " 📌 Expected: " << expected << "\n";
|
||||
std::cerr << " ❌ Got: " << actual << "\n";
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
extern Zone *zone;
|
||||
|
||||
inline void SetupZone(std::string zone_short_name, uint32 instance_id = 0) {
|
||||
LogSys.SilenceConsoleLogging();
|
||||
|
||||
LogSys.log_settings[Logs::ZoneState].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||
LogSys.log_settings[Logs::Info].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||
LogSys.log_settings[Logs::Spawns].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||
|
||||
// boot shell zone for testing
|
||||
Zone::Bootup(ZoneID(zone_short_name), 0, false);
|
||||
zone->StopShutdownTimer();
|
||||
entity_list.Process();
|
||||
entity_list.MobProcess();
|
||||
|
||||
LogSys.EnableConsoleLogging();
|
||||
}
|
||||
@@ -1,25 +1,13 @@
|
||||
#include "../../common/http/httplib.h"
|
||||
#include "../../common/eqemu_logsys.h"
|
||||
#include "../../common/platform.h"
|
||||
#include "../zone.h"
|
||||
#include "../client.h"
|
||||
#include "../../zone.h"
|
||||
#include "../../client.h"
|
||||
#include "../../common/net/eqstream.h"
|
||||
|
||||
extern Zone *zone;
|
||||
|
||||
void RunTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
||||
{
|
||||
if (expected == actual) {
|
||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||
} else {
|
||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||
std::cerr << " 📌 Expected: " << expected << "\n";
|
||||
std::cerr << " ❌ Got: " << actual << "\n";
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void ZoneCLI::DataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||
void ZoneCLI::TestDataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||
{
|
||||
if (cmd[{"-h", "--help"}]) {
|
||||
return;
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "../../common/http/httplib.h"
|
||||
#include "../../common/eqemu_logsys.h"
|
||||
#include "../../common/platform.h"
|
||||
#include "../zone.h"
|
||||
#include "../client.h"
|
||||
#include "../../zone.h"
|
||||
#include "../../client.h"
|
||||
#include "../../common/net/eqstream.h"
|
||||
#include "../../common/json/json.hpp"
|
||||
|
||||
@@ -36,19 +36,6 @@ struct TestCase {
|
||||
bool handin_check_result;
|
||||
};
|
||||
|
||||
void RunTest(const std::string &test_name, bool expected, bool actual)
|
||||
{
|
||||
if (expected == actual) {
|
||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||
}
|
||||
else {
|
||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||
std::cerr << " 📌 Expected: " << (expected ? "true" : "false") << "\n";
|
||||
std::cerr << " ❌ Got: " << (actual ? "true" : "false") << "\n";
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void RunSerializedTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
||||
{
|
||||
if (expected == actual) {
|
||||
@@ -75,7 +62,7 @@ std::string SerializeHandin(const std::map<std::string, uint32> &items, const Ha
|
||||
return j.dump();
|
||||
}
|
||||
|
||||
void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||
void ZoneCLI::TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||
{
|
||||
if (cmd[{"-h", "--help"}]) {
|
||||
return;
|
||||
@@ -1,13 +1,13 @@
|
||||
#include "../../common/http/httplib.h"
|
||||
#include "../../common/eqemu_logsys.h"
|
||||
#include "../../common/platform.h"
|
||||
#include "../zone.h"
|
||||
#include "../client.h"
|
||||
#include "../../zone.h"
|
||||
#include "../../client.h"
|
||||
#include "../../common/net/eqstream.h"
|
||||
|
||||
extern Zone *zone;
|
||||
|
||||
void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||
void ZoneCLI::TestNpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||
{
|
||||
if (cmd[{"-h", "--help"}]) {
|
||||
return;
|
||||
File diff suppressed because it is too large
Load Diff
+92
-18
@@ -882,9 +882,13 @@ void Client::SendZoneInPackets()
|
||||
//SendGuildMembers();
|
||||
SendGuildURL();
|
||||
SendGuildChannel();
|
||||
SendGuildLFGuildStatus();
|
||||
if (RuleB(Guild, EnableLFGuild)) {
|
||||
SendGuildLFGuildStatus();
|
||||
}
|
||||
}
|
||||
if (RuleB(Guild, EnableLFGuild)) {
|
||||
SendLFGuildStatus();
|
||||
}
|
||||
SendLFGuildStatus();
|
||||
|
||||
//No idea why live sends this if even were not in a guild
|
||||
SendGuildMOTD();
|
||||
@@ -2549,40 +2553,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 +4760,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();
|
||||
|
||||
+34
-32
@@ -120,48 +120,46 @@ int Client::GetBotSpawnLimit(uint8 class_id) {
|
||||
}
|
||||
|
||||
const auto& zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ",");
|
||||
const auto& zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ",");
|
||||
int i = 0;
|
||||
|
||||
for (const auto& result : zones_list) {
|
||||
try {
|
||||
if (
|
||||
std::stoul(result) == zone->GetZoneID() &&
|
||||
std::stoul(zones_limits_list[i]) < bot_spawn_limit
|
||||
) {
|
||||
bot_spawn_limit = std::stoul(zones_limits_list[i]);
|
||||
if (!zones_list.empty()) {
|
||||
auto it = std::find(zones_list.begin(), zones_list.end(), std::to_string(zone->GetZoneID()));
|
||||
|
||||
break;
|
||||
if (it != zones_list.end()) {
|
||||
const auto& zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ",");
|
||||
|
||||
if (zones_list.size() == zones_limits_list.size()) {
|
||||
try {
|
||||
auto new_limit = std::stoul(zones_limits_list[std::distance(zones_list.begin(), it)]);
|
||||
|
||||
if (new_limit < bot_spawn_limit) {
|
||||
bot_spawn_limit = new_limit;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LogInfo("Invalid entry in Rule Bots:ZoneSpawnLimits: [{}]", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
catch (const std::exception& e) {
|
||||
LogInfo("Invalid entry in Rule VegasScaling:SpecialScalingZones or SpecialScalingZonesVersions: [{}]", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
const auto& zones_forced_list = Strings::Split(RuleS(Bots, ZonesWithForcedSpawnLimits), ",");
|
||||
const auto& zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ",");
|
||||
i = 0;
|
||||
|
||||
for (const auto& result : zones_forced_list) {
|
||||
try {
|
||||
if (
|
||||
std::stoul(result) == zone->GetZoneID() &&
|
||||
std::stoul(zones_forced_limits_list[i]) != bot_spawn_limit
|
||||
) {
|
||||
bot_spawn_limit = std::stoul(zones_forced_limits_list[i]);
|
||||
if (!zones_forced_list.empty()) {
|
||||
auto it = std::find(zones_forced_list.begin(), zones_forced_list.end(), std::to_string(zone->GetZoneID()));
|
||||
|
||||
break;
|
||||
if (it != zones_forced_list.end()) {
|
||||
const auto& zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ",");
|
||||
|
||||
if (zones_forced_list.size() == zones_forced_limits_list.size()) {
|
||||
try {
|
||||
auto new_limit = std::stoul(zones_forced_limits_list[std::distance(zones_forced_list.begin(), it)]);
|
||||
|
||||
if (new_limit != bot_spawn_limit) {
|
||||
bot_spawn_limit = new_limit;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LogInfo("Invalid entry in Rule Bots:ZoneForcedSpawnLimits: [{}]", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
catch (const std::exception& e) {
|
||||
LogInfo("Invalid entry in Rule VegasScaling:SpecialScalingZones or SpecialScalingZonesVersions: [{}]", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +256,8 @@ int Client::GetDefaultBotSettings(uint8 setting_type, uint16 bot_setting) {
|
||||
return GetDefaultSpellTypeMinThreshold(bot_setting);
|
||||
case BotSettingCategories::SpellMaxThreshold:
|
||||
return GetDefaultSpellTypeMaxThreshold(bot_setting);
|
||||
default:
|
||||
return 0; // default return for any unsupported setting type
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,6 +271,8 @@ int Client::GetBotSetting(uint8 setting_type, uint16 bot_setting) {
|
||||
return GetSpellTypeMinThreshold(bot_setting);
|
||||
case BotSettingCategories::SpellMaxThreshold:
|
||||
return GetSpellTypeMaxThreshold(bot_setting);
|
||||
default:
|
||||
return 0; // default return for any unsupported setting type
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+37
-14
@@ -73,6 +73,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "../common/repositories/character_stats_record_repository.h"
|
||||
#include "dialogue_window.h"
|
||||
#include "../common/rulesys.h"
|
||||
#include "../common/repositories/adventure_members_repository.h"
|
||||
|
||||
extern QueryServ* QServ;
|
||||
extern Zone* zone;
|
||||
@@ -826,6 +827,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)) {
|
||||
@@ -909,11 +914,14 @@ void Client::CompleteConnect()
|
||||
|
||||
SendDynamicZoneUpdates();
|
||||
|
||||
/** Request adventure info **/
|
||||
auto pack = new ServerPacket(ServerOP_AdventureDataRequest, 64);
|
||||
strcpy((char*)pack->pBuffer, GetName());
|
||||
worldserver.SendPacket(pack);
|
||||
delete pack;
|
||||
// Request adventure info
|
||||
auto members = AdventureMembersRepository::GetWhere(database, fmt::format("charid = {}", CharacterID()));
|
||||
if (!members.empty()) {
|
||||
auto pack = new ServerPacket(ServerOP_AdventureDataRequest, 64);
|
||||
strcpy((char*)pack->pBuffer, GetName());
|
||||
worldserver.SendPacket(pack);
|
||||
delete pack;
|
||||
}
|
||||
|
||||
if (IsClient() && CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater) {
|
||||
EQApplicationPacket *outapp = MakeBuffsPacket(false);
|
||||
@@ -4256,7 +4264,7 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app)
|
||||
else {
|
||||
OnDisconnect(true);
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4548,14 +4556,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 +6784,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;
|
||||
@@ -7961,7 +7984,7 @@ void Client::Handle_OP_GuildCreate(const EQApplicationPacket *app)
|
||||
}
|
||||
|
||||
SetGuildID(new_guild_id);
|
||||
SendGuildList();
|
||||
UpdateWho();
|
||||
guild_mgr.MemberAdd(new_guild_id, CharacterID(), GetLevel(), GetClass(), GUILD_LEADER, GetZoneID(), GetName());
|
||||
guild_mgr.SendGuildRefresh(new_guild_id, true, true, true, true);
|
||||
guild_mgr.SendToWorldSendGuildList();
|
||||
@@ -8130,7 +8153,7 @@ void Client::Handle_OP_GuildInvite(const EQApplicationPacket *app)
|
||||
if (!invitee) {
|
||||
Message(
|
||||
Chat::Red,
|
||||
"Prospective guild member %s must be in zone to preform guild operations on them.",
|
||||
"Prospective guild member %s must be in zone to perform guild operations on them.",
|
||||
gc->othername
|
||||
);
|
||||
return;
|
||||
@@ -11043,7 +11066,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
|
||||
if (!target)
|
||||
break;
|
||||
|
||||
if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(target) || !CheckLosCheat(target))) {
|
||||
if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(target)) {
|
||||
mypet->SayString(this, NOT_LEGAL_TARGET);
|
||||
break;
|
||||
}
|
||||
@@ -11111,7 +11134,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
|
||||
break;
|
||||
}
|
||||
|
||||
if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(GetTarget()) || !CheckLosCheat(GetTarget()))) {
|
||||
if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(GetTarget())) {
|
||||
mypet->SayString(this, NOT_LEGAL_TARGET);
|
||||
break;
|
||||
}
|
||||
@@ -15463,7 +15486,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;
|
||||
|
||||
+1
-1
@@ -245,7 +245,7 @@ int command_init(void)
|
||||
command_add("zonebootup", "[ZoneServerID] [shortname] - Make a zone server boot a specific zone", AccountStatus::GMLeadAdmin, command_zonebootup) ||
|
||||
command_add("zoneinstance", "[Instance ID] [X] [Y] [Z] - Teleport to specified Instance by ID (coordinates are optional)", AccountStatus::Guide, command_zone_instance) ||
|
||||
command_add("zoneshard", "[zone] [instance_id] - Teleport explicitly to a zone shard", AccountStatus::Player, command_zone_shard) ||
|
||||
command_add("zoneshutdown", "[shortname] - Shut down a zone server", AccountStatus::GMLeadAdmin, command_zoneshutdown) ||
|
||||
command_add("zoneshutdown", "[instance|zone] [Instance ID|Zone ID|Zone Short Name] - Shut down a zone server by Instance ID, Zone ID, or Zone Short Name", AccountStatus::GMLeadAdmin, command_zoneshutdown) ||
|
||||
command_add("zsave", " Saves zheader to the database", AccountStatus::QuestTroupe, command_zsave)
|
||||
) {
|
||||
command_deinit();
|
||||
|
||||
@@ -111,7 +111,6 @@ void command_ipban(Client *c, const Seperator *sep);
|
||||
void command_kick(Client *c, const Seperator *sep);
|
||||
void command_killallnpcs(Client *c, const Seperator *sep);
|
||||
void command_kill(Client *c, const Seperator *sep);
|
||||
void command_level(Client *c, const Seperator *sep);
|
||||
void command_list(Client *c, const Seperator *sep);
|
||||
void command_lootsim(Client *c, const Seperator *sep);
|
||||
void command_load_shared_memory(Client *c, const Seperator *sep);
|
||||
@@ -139,7 +138,6 @@ void command_nudge(Client *c, const Seperator *sep);
|
||||
void command_nukebuffs(Client *c, const Seperator *sep);
|
||||
void command_nukeitem(Client *c, const Seperator *sep);
|
||||
void command_object(Client *c, const Seperator *sep);
|
||||
void command_oocmute(Client *c, const Seperator *sep);
|
||||
void command_parcels(Client *c, const Seperator *sep);
|
||||
void command_path(Client *c, const Seperator *sep);
|
||||
void command_peqzone(Client *c, const Seperator *sep);
|
||||
@@ -147,7 +145,6 @@ void command_petitems(Client *c, const Seperator *sep);
|
||||
void command_picklock(Client *c, const Seperator *sep);
|
||||
void command_profanity(Client *c, const Seperator *sep);
|
||||
void command_push(Client *c, const Seperator *sep);
|
||||
void command_pvp(Client *c, const Seperator *sep);
|
||||
void command_raidloot(Client* c, const Seperator* sep);
|
||||
void command_randomfeatures(Client *c, const Seperator *sep);
|
||||
void command_refreshgroup(Client *c, const Seperator *sep);
|
||||
@@ -201,8 +198,6 @@ void command_zone_instance(Client *c, const Seperator *sep);
|
||||
void command_zone_shard(Client *c, const Seperator *sep);
|
||||
void command_zonebootup(Client *c, const Seperator *sep);
|
||||
void command_zoneshutdown(Client *c, const Seperator *sep);
|
||||
void command_zopp(Client *c, const Seperator *sep);
|
||||
void command_zsafecoords(Client *c, const Seperator *sep);
|
||||
void command_zsave(Client *c, const Seperator *sep);
|
||||
|
||||
#include "bot.h"
|
||||
|
||||
+2
-2
@@ -290,7 +290,7 @@ Corpse::Corpse(Client *c, int32 rez_exp, KilledByTypes in_killed_by) : Mob(
|
||||
m_corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS));
|
||||
m_loot_cooldown_timer.SetTimer(10);
|
||||
m_check_rezzable_timer.SetTimer(1000);
|
||||
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineTime));
|
||||
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineCheckTime) * 1000);
|
||||
|
||||
m_corpse_rezzable_timer.Disable();
|
||||
SetRezTimer(true);
|
||||
@@ -583,7 +583,7 @@ Corpse::Corpse(
|
||||
m_corpse_delay_timer.SetTimer(RuleI(NPC, CorpseUnlockTimer));
|
||||
m_corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS));
|
||||
m_loot_cooldown_timer.SetTimer(10);
|
||||
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineTime));
|
||||
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineCheckTime) * 1000);
|
||||
m_check_rezzable_timer.SetTimer(1000);
|
||||
m_corpse_rezzable_timer.Disable();
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <numbers>
|
||||
|
||||
#define OPEN_DOOR 0x02
|
||||
#define CLOSE_DOOR 0x03
|
||||
#define OPEN_INVDOOR 0x03
|
||||
@@ -970,3 +973,68 @@ bool Doors::GetIsDoorBlacklisted()
|
||||
bool Doors::IsDoorBlacklisted() {
|
||||
return m_is_blacklisted_to_open;
|
||||
}
|
||||
|
||||
bool Doors::IsDoorBetween(glm::vec4 loc_a, glm::vec4 loc_c, uint16 door_size, float door_depth, bool draw_box) {
|
||||
glm::vec4 door_loc = GetPosition();
|
||||
float half_size = door_size * 0.5f;
|
||||
float half_depth = door_depth * 0.5f;
|
||||
float normalized_heading = std::fmod(door_loc.w, 512.0f);
|
||||
float heading_radians = normalized_heading * (std::numbers::pi / 256.0f);
|
||||
glm::mat4 door_rotation = glm::rotate(glm::mat4(1.0f), -heading_radians, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
glm::vec3 box_corner_one = glm::vec3(door_size, -half_depth, 0.0f);
|
||||
glm::vec3 box_corner_two = glm::vec3(-door_size, -half_depth, 0.0f);
|
||||
glm::vec3 box_corner_three = glm::vec3(-door_size, half_depth, 0.0f);
|
||||
glm::vec3 box_corner_four = glm::vec3(door_size, half_depth, 0.0f);
|
||||
glm::vec3 door_center_offset = glm::vec3(-(door_size * 0.75f), half_depth * 0.5f, 0.0f);
|
||||
glm::vec3 door_center = glm::vec3(door_loc) + glm::vec3(door_rotation * glm::vec4(door_center_offset, 1.0f));
|
||||
glm::mat4 transform = glm::translate(glm::mat4(1.0f), door_center) * door_rotation;
|
||||
|
||||
box_corner_one = glm::vec3(transform * glm::vec4(box_corner_one, 1.0f));
|
||||
box_corner_two = glm::vec3(transform * glm::vec4(box_corner_two, 1.0f));
|
||||
box_corner_three = glm::vec3(transform * glm::vec4(box_corner_three, 1.0f));
|
||||
box_corner_four = glm::vec3(transform * glm::vec4(box_corner_four, 1.0f));
|
||||
|
||||
if (draw_box) {
|
||||
NPC::SpawnZonePointNodeNPC("loc_a", loc_a);
|
||||
NPC::SpawnZonePointNodeNPC("door_anchor", door_loc);
|
||||
NPC::SpawnZonePointNodeNPC("loc_c", loc_c);
|
||||
NPC::SpawnZonePointNodeNPC("box_corner_one", glm::vec4(box_corner_one.x, box_corner_one.y, box_corner_one.z, 0));
|
||||
NPC::SpawnZonePointNodeNPC("box_corner_two", glm::vec4(box_corner_two.x, box_corner_two.y, box_corner_two.z, 0));
|
||||
NPC::SpawnZonePointNodeNPC("box_corner_three", glm::vec4(box_corner_three.x, box_corner_three.y, box_corner_three.z, 0));
|
||||
NPC::SpawnZonePointNodeNPC("box_corner_four", glm::vec4(box_corner_four.x, box_corner_four.y, box_corner_four.z, 0));
|
||||
NPC::SpawnZonePointNodeNPC("box_center", glm::vec4(door_center.x, door_center.y, door_center.z, 0));
|
||||
}
|
||||
|
||||
// Check if LoS intersects box
|
||||
auto intersects_box = [](const glm::vec3& a, const glm::vec3& b, const glm::vec3& p1, const glm::vec3& p2) {
|
||||
glm::vec3 ab = b - a;
|
||||
glm::vec3 p1p2 = p2 - p1;
|
||||
|
||||
glm::vec3 cross = glm::cross(ab, p1p2);
|
||||
float cross_magnitude_squared = glm::dot(cross, cross);
|
||||
|
||||
if (cross_magnitude_squared < 1e-6f) {
|
||||
return false; // Lines are parallel or coincident
|
||||
}
|
||||
|
||||
float t = glm::dot(glm::cross(p1 - a, p1p2), cross) / cross_magnitude_squared;
|
||||
float u = glm::dot(glm::cross(p1 - a, ab), cross) / cross_magnitude_squared;
|
||||
|
||||
return (t >= 0.0f && t <= 1.0f && u >= 0.0f && u <= 1.0f);
|
||||
};
|
||||
|
||||
// Check intersection with each edge of the door bounding box
|
||||
glm::vec3 loc_a_vec3(loc_a.x, loc_a.y, loc_a.z);
|
||||
glm::vec3 loc_c_vec3(loc_c.x, loc_c.y, loc_c.z);
|
||||
|
||||
if (
|
||||
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_one, box_corner_two) ||
|
||||
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_two, box_corner_three) ||
|
||||
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_three, box_corner_four) ||
|
||||
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_four, box_corner_one)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ public:
|
||||
bool IsDestinationZoneSame() const;
|
||||
|
||||
bool IsDoorBlacklisted();
|
||||
bool IsDoorBetween(glm::vec4 loc_a, glm::vec4 loc_c, uint16 door_size = 15, float door_depth = 5.0f, bool draw_box = false);
|
||||
|
||||
const char* GetDoorZone() const { return m_zone_name; }
|
||||
|
||||
|
||||
+19
-5
@@ -3143,20 +3143,23 @@ void EntityList::Depop(bool StartSpawnTimer)
|
||||
{
|
||||
for (auto it = npc_list.begin(); it != npc_list.end(); ++it) {
|
||||
NPC *pnpc = it->second;
|
||||
|
||||
if (pnpc) {
|
||||
Mob *own = pnpc->GetOwner();
|
||||
//do not depop player's pets...
|
||||
if (own && own->IsClient())
|
||||
//do not depop player/bot pets...
|
||||
if (own && own->IsOfClientBot()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pnpc->IsHorse())
|
||||
if (pnpc->IsHorse()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pnpc->IsFindable())
|
||||
if (pnpc->IsFindable()) {
|
||||
UpdateFindableNPCState(pnpc, true);
|
||||
}
|
||||
|
||||
pnpc->WipeHateList();
|
||||
|
||||
pnpc->Depop(StartSpawnTimer);
|
||||
}
|
||||
}
|
||||
@@ -5988,3 +5991,14 @@ void EntityList::SendMerchantInventory(Mob* m, int32 slot_id, bool is_delete)
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void EntityList::RestoreCorpse(NPC *npc, uint32_t decay_time)
|
||||
{
|
||||
uint16 corpse_id = npc->GetID();
|
||||
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
||||
auto c = entity_list.GetCorpseByID(corpse_id);
|
||||
if (c) {
|
||||
c->UnLock();
|
||||
c->SetDecayTimer(decay_time);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,6 +580,7 @@ public:
|
||||
|
||||
void SendMerchantEnd(Mob* merchant);
|
||||
void SendMerchantInventory(Mob* m, int32 slot_id = -1, bool is_delete = false);
|
||||
void RestoreCorpse(NPC* npc, uint32_t decay_time);
|
||||
|
||||
protected:
|
||||
friend class Zone;
|
||||
|
||||
@@ -35,6 +35,26 @@ void DoorManipulation::CommandHandler(Client *c, const Seperator *sep)
|
||||
);
|
||||
}
|
||||
|
||||
if (arg1 == "drawbox") {
|
||||
Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId());
|
||||
|
||||
if (door) {
|
||||
uint16 door_size = 15;
|
||||
float door_depth = 5.0f;
|
||||
|
||||
if (sep->IsNumber(2) && atof(sep->arg[2]) > 0) {
|
||||
door_size = atof(sep->arg[2]);
|
||||
}
|
||||
|
||||
if (sep->IsNumber(3) && atof(sep->arg[3]) > 0) {
|
||||
door_depth = atof(sep->arg[3]);
|
||||
}
|
||||
|
||||
|
||||
door->IsDoorBetween(c->GetPosition(), (c->GetTarget() ? c->GetTarget()->GetPosition() : c->GetPosition()), door_size, door_depth, true);
|
||||
}
|
||||
}
|
||||
|
||||
// edit menu
|
||||
if (arg1 == "edit") {
|
||||
Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId());
|
||||
@@ -544,6 +564,13 @@ void DoorManipulation::CommandHandler(Client *c, const Seperator *sep)
|
||||
c->Message(Chat::White, "#door setinvertstate [0|1] | Sets selected door invert state");
|
||||
c->Message(Chat::White, "#door setincline <incline> | Sets selected door incline");
|
||||
c->Message(Chat::White, "#door opentype <opentype> | Sets selected door opentype");
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"{} <door_size> <door_depth> | Draws a box for the door, default size = 15, depth = 5 if none defined",
|
||||
Saylink::Silent("#door drawbox")
|
||||
).c_str()
|
||||
);
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "show/zone_loot.cpp"
|
||||
#include "show/zone_points.cpp"
|
||||
#include "show/zone_status.cpp"
|
||||
#include "show/zone_variables.cpp"
|
||||
|
||||
void command_show(Client *c, const Seperator *sep)
|
||||
{
|
||||
@@ -110,6 +111,7 @@ void command_show(Client *c, const Seperator *sep)
|
||||
Cmd{.cmd = "zone_loot", .u = "zone_loot", .fn = ShowZoneLoot, .a = {"#viewzoneloot"}},
|
||||
Cmd{.cmd = "zone_points", .u = "zone_points", .fn = ShowZonePoints, .a = {"#showzonepoints"}},
|
||||
Cmd{.cmd = "zone_status", .u = "zone_status", .fn = ShowZoneStatus, .a = {"#zonestatus"}},
|
||||
Cmd{.cmd = "zone_variables", .u = "zone_variables", .fn = ShowZoneVariables},
|
||||
};
|
||||
|
||||
// Check for arguments
|
||||
|
||||
@@ -81,11 +81,6 @@ void ShowZoneData(Client *c, const Seperator *sep)
|
||||
DialogueWindow::TableCell(std::to_string(zone->newzone_data.time_type))
|
||||
);
|
||||
|
||||
popup_table += DialogueWindow::TableRow(
|
||||
DialogueWindow::TableCell("Time Type") +
|
||||
DialogueWindow::TableCell(std::to_string(zone->newzone_data.time_type))
|
||||
);
|
||||
|
||||
popup_table += DialogueWindow::TableRow(
|
||||
DialogueWindow::TableCell("Experience Multiplier") +
|
||||
DialogueWindow::TableCell(
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "../../client.h"
|
||||
#include "../../zone.h"
|
||||
|
||||
extern Zone* zone;
|
||||
|
||||
void ShowZoneVariables(Client *c, const Seperator *sep)
|
||||
{
|
||||
if (!zone->GetVariables().empty()) {
|
||||
c->Message(Chat::White, "Zone Variables:");
|
||||
for (auto &key: zone->GetVariables()) {
|
||||
c->Message(Chat::White, fmt::format("{}: {}", key, zone->GetVariable(key)).c_str());
|
||||
}
|
||||
} else {
|
||||
c->Message(Chat::White, "No zone variables set.");
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,18 @@ extern WorldServer worldserver;
|
||||
void command_zoneshutdown(Client *c, const Seperator *sep)
|
||||
{
|
||||
const int arguments = sep->argnum;
|
||||
if (!arguments) {
|
||||
c->Message(Chat::White, "Usage: #zoneshutdown [Zone ID|Zone Short Name]");
|
||||
if (arguments < 2) {
|
||||
c->Message(Chat::White, "Usage: #zoneshutdown instance [Instance ID]");
|
||||
c->Message(Chat::White, "Usage: #zoneshutdown zone [Zone ID|Zone Short Name]");
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_instance = !strcasecmp(sep->arg[1], "instance");
|
||||
bool is_zone = !strcasecmp(sep->arg[1], "zone");
|
||||
|
||||
if (!is_instance && !is_zone) {
|
||||
c->Message(Chat::White, "Usage: #zoneshutdown instance [Instance ID]");
|
||||
c->Message(Chat::White, "Usage: #zoneshutdown zone [Zone ID|Zone Short Name]");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -16,23 +26,55 @@ void command_zoneshutdown(Client *c, const Seperator *sep)
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32 zone_id = sep->IsNumber(1) ? Strings::ToUnsignedInt(sep->arg[1]) : ZoneID(sep->arg[1]);
|
||||
uint32 zone_id = 0;
|
||||
uint16 instance_id = 0;
|
||||
std::string message = "";
|
||||
|
||||
if (!zone_id) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Zone '{}' does not exist.",
|
||||
sep->arg[1]
|
||||
).c_str()
|
||||
);
|
||||
return;
|
||||
if (is_instance) {
|
||||
instance_id = sep->IsNumber(2) ? Strings::ToUnsignedInt(sep->arg[2]) : 0;
|
||||
|
||||
if (!database.CheckInstanceExists(instance_id)) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Instance ID '{}' does not exist.",
|
||||
instance_id
|
||||
).c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
message = fmt::format("Instance ID {}", instance_id);
|
||||
} else if (is_zone) {
|
||||
zone_id = sep->IsNumber(2) ? Strings::ToUnsignedInt(sep->arg[2]) : ZoneID(sep->arg[2]);
|
||||
|
||||
if (!zone_id) {
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Zone '{}' does not exist.",
|
||||
sep->arg[1]
|
||||
).c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
message = fmt::format("{} (ID {})", ZoneLongName(zone_id), zone_id);
|
||||
}
|
||||
|
||||
c->Message(
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
"Attempting to shut down {}.",
|
||||
message
|
||||
).c_str()
|
||||
);
|
||||
|
||||
auto pack = new ServerPacket(ServerOP_ZoneShutdown, sizeof(ServerZoneStateChange_Struct));
|
||||
auto *s = (ServerZoneStateChange_Struct *) pack->pBuffer;
|
||||
|
||||
s->zone_id = zone_id;
|
||||
s->zone_id = zone_id;
|
||||
s->instance_id = instance_id;
|
||||
|
||||
strn0cpy(s->admin_name, c->GetName(), sizeof(s->admin_name));
|
||||
|
||||
|
||||
+1
-1
@@ -195,7 +195,7 @@ void Client::SendGuildList()
|
||||
|
||||
std::stringstream ss;
|
||||
cereal::BinaryOutputArchive ar(ss);
|
||||
ar(guilds_list);
|
||||
{ ar(guilds_list); }
|
||||
|
||||
uint32 packet_size = ss.str().length();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+11
-1
@@ -500,6 +500,9 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
Timer InterserverTimer(INTERSERVER_TIMER); // does MySQL pings and auto-reconnect
|
||||
Timer UpdateWhoTimer(RuleI(Zone, UpdateWhoTimer) * 1000); // updates who list every 2 minutes
|
||||
Timer WorldserverProcess(1000);
|
||||
|
||||
#ifdef EQPROFILE
|
||||
#ifdef PROFILE_DUMP_TIME
|
||||
Timer profile_dump_timer(PROFILE_DUMP_TIME * 1000);
|
||||
@@ -615,6 +618,10 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (WorldserverProcess.Check()) {
|
||||
worldserver.Process();
|
||||
}
|
||||
|
||||
if (is_zone_loaded) {
|
||||
{
|
||||
entity_list.GroupProcess();
|
||||
@@ -647,7 +654,10 @@ int main(int argc, char **argv)
|
||||
InterserverTimer.Start();
|
||||
database.ping();
|
||||
content_db.ping();
|
||||
entity_list.UpdateWho();
|
||||
if (UpdateWhoTimer.Check()) {
|
||||
UpdateWhoTimer.SetTimer(RuleI(Zone, UpdateWhoTimer) * 1000); // in-case it was changed
|
||||
entity_list.UpdateWho();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+40
-47
@@ -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();
|
||||
|
||||
@@ -8672,56 +8675,54 @@ bool Mob::IsInGroupOrRaid(Mob* other, bool same_raid_group) {
|
||||
|
||||
bool Mob::DoLosChecks(Mob* other) {
|
||||
if (!CheckLosFN(other) || !CheckWaterLoS(other)) {
|
||||
if (CheckLosCheatExempt(other)) {
|
||||
if (RuleB(Map, EnableLoSCheatExemptions) && CheckLosCheatExempt(other)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckLosCheat(other)) {
|
||||
if (RuleB(Map, CheckForDoorLoSCheat) && !CheckDoorLoSCheat(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mob::CheckLosCheat(Mob* other) {
|
||||
if (RuleB(Map, CheckForLoSCheat)) {
|
||||
for (auto itr : entity_list.GetDoorsList()) {
|
||||
Doors* d = itr.second;
|
||||
bool Mob::CheckDoorLoSCheat(Mob* other) {
|
||||
if (!other->IsOfClientBotMerc() && other->CastToNPC()->IsOnHatelist(this)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string& zones_to_check = RuleS(Map, ZonesToCheckDoorCheat);
|
||||
|
||||
if (zones_to_check.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& v = Strings::Split(zones_to_check, ",");
|
||||
|
||||
if (zones_to_check == "all" || std::find(v.begin(), v.end(), std::to_string(zone->GetZoneID())) != v.end()) {
|
||||
for (auto itr: entity_list.GetDoorsList()) {
|
||||
Doors *d = itr.second;
|
||||
|
||||
if (
|
||||
!d->IsDoorOpen() &&
|
||||
(
|
||||
d->GetKeyItem() ||
|
||||
d->GetLockpick() ||
|
||||
d->IsDoorOpen() ||
|
||||
d->IsDoorBlacklisted() ||
|
||||
d->GetNoKeyring() != 0 ||
|
||||
d->GetDoorParam() > 0
|
||||
d->GetNoKeyring() != 0
|
||||
)
|
||||
) {
|
||||
// If the door is a trigger door, check if the trigger door is open
|
||||
if (d->GetTriggerDoorID() > 0) {
|
||||
auto td = entity_list.GetDoorsByDoorID(d->GetTriggerDoorID());
|
||||
) {
|
||||
float distance = Distance(m_Position, d->GetPosition());
|
||||
|
||||
if (td) {
|
||||
if (Strings::RemoveNumbers(d->GetDoorName()) != Strings::RemoveNumbers(td->GetDoorName())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (distance > RuleR(Map, RangeCheckForDoorLoSCheat) || !CheckLosFN(d->GetX(), d->GetY(), d->GetZ(), GetSize())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (DistanceNoZ(GetPosition(), d->GetPosition()) <= 50) {
|
||||
auto who_to_door = DistanceNoZ(GetPosition(), d->GetPosition());
|
||||
auto other_to_door = DistanceNoZ(other->GetPosition(), d->GetPosition());
|
||||
auto who_to_other = DistanceNoZ(GetPosition(), other->GetPosition());
|
||||
auto distance_difference = who_to_other - (who_to_door + other_to_door);
|
||||
|
||||
if (distance_difference >= (-1 * RuleR(Maps, RangeCheckForLoSCheat)) && distance_difference <= RuleR(Maps, RangeCheckForLoSCheat)) {
|
||||
return false;
|
||||
}
|
||||
if (d->IsDoorBetween(GetPosition(), other->GetPosition(), d->GetSize())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8730,26 +8731,18 @@ bool Mob::CheckLosCheat(Mob* other) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mob::CheckLosCheatExempt(Mob* other)
|
||||
{
|
||||
if (RuleB(Map, EnableLoSCheatExemptions)) {
|
||||
/* This is an exmaple of how to configure exemptions for LoS checks.
|
||||
glm::vec4 exempt_check_who;
|
||||
glm::vec4 exempt_check_other;
|
||||
bool Mob::CheckLosCheatExempt(Mob* other) {
|
||||
glm::vec4 exempt_check_who;
|
||||
|
||||
switch (zone->GetZoneID()) {
|
||||
case POEARTHB:
|
||||
exempt_check_who.x = 2051; exempt_check_who.y = 407; exempt_check_who.z = -219; //Middle of councilman spawns
|
||||
//exempt_check_other.x = 1455; exempt_check_other.y = 415; exempt_check_other.z = -242;
|
||||
//check to be sure the player and the target are outside of the councilman area
|
||||
//if the player is inside the cove they cannot be higher than the ceiling (no exploiting from uptop)
|
||||
if (GetZ() <= -171 && other->GetZ() <= -171 && DistanceNoZ(other->GetPosition(), exempt_check_who) <= 800 && DistanceNoZ(GetPosition(), exempt_check_who) <= 800) {
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
switch (zone->GetZoneID()) {
|
||||
case Zones::POEARTHB:
|
||||
exempt_check_who.x = 2053; exempt_check_who.y = 408; exempt_check_who.z = -219; //Middle of councilman spawns
|
||||
//if the player is inside the cove they cannot be higher than the ceiling (no exploiting from uptop) --- 800 from center of council to furthest corner in cove
|
||||
if (GetZ() <= -171 && other->GetZ() <= -171 && DistanceNoZ(other->GetPosition(), exempt_check_who) <= 800 && DistanceNoZ(GetPosition(), exempt_check_who) <= 800) {
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
+4
-2
@@ -798,7 +798,7 @@ public:
|
||||
static bool CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarget, float sizeTarget);
|
||||
virtual bool CheckWaterLoS(Mob* m);
|
||||
bool CheckPositioningLosFN(Mob* other, float posX, float posY, float posZ);
|
||||
bool CheckLosCheat(Mob* other); //door skipping checks for LoS
|
||||
bool CheckDoorLoSCheat(Mob* other); //door skipping checks for LoS
|
||||
bool CheckLosCheatExempt(Mob* other); //exemptions to bypass los
|
||||
bool DoLosChecks(Mob* other);
|
||||
inline void SetLastLosState(bool value) { last_los_check = value; }
|
||||
@@ -1125,7 +1125,7 @@ public:
|
||||
|
||||
virtual void SetAttackTimer();
|
||||
inline void SetInvul(bool invul) { invulnerable=invul; }
|
||||
inline bool GetInvul(void) { return invulnerable; }
|
||||
inline bool GetInvul() { return invulnerable; }
|
||||
void SetExtraHaste(int haste, bool need_to_save = true);
|
||||
inline int GetExtraHaste() { return extra_haste; }
|
||||
virtual int GetHaste();
|
||||
@@ -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);
|
||||
|
||||
@@ -132,8 +132,6 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
||||
),
|
||||
attacked_timer(CombatEventTimer_expire),
|
||||
swarm_timer(100),
|
||||
m_corpse_queue_timer(1000),
|
||||
m_corpse_queue_shutoff_timer(30000),
|
||||
m_resumed_from_zone_suspend_shutoff_timer(10000),
|
||||
classattack_timer(1000),
|
||||
monkattack_timer(1000),
|
||||
@@ -624,28 +622,6 @@ bool NPC::Process()
|
||||
|
||||
// zone state corpse creation timer
|
||||
if (RuleB(Zone, StateSavingOnShutdown)) {
|
||||
// creates a corpse if the NPC is queued for corpse creation
|
||||
if (m_corpse_queue_timer.Check()) {
|
||||
if (IsQueuedForCorpse()) {
|
||||
auto decay_timer = m_corpse_decay_time;
|
||||
uint16 corpse_id = GetID();
|
||||
Death(this, GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
||||
auto c = entity_list.GetCorpseByID(corpse_id);
|
||||
if (c) {
|
||||
c->UnLock();
|
||||
c->SetDecayTimer(decay_timer);
|
||||
}
|
||||
}
|
||||
m_corpse_queue_timer.Disable();
|
||||
m_corpse_queue_shutoff_timer.Disable();
|
||||
}
|
||||
|
||||
// shuts off the corpse queue timer if it is still running
|
||||
if (m_corpse_queue_shutoff_timer.Check()) {
|
||||
m_corpse_queue_timer.Disable();
|
||||
m_corpse_queue_shutoff_timer.Disable();
|
||||
}
|
||||
|
||||
// shuts off the temporary spawn protected state of the NPC
|
||||
if (m_resumed_from_zone_suspend_shutoff_timer.Check()) {
|
||||
m_resumed_from_zone_suspend_shutoff_timer.Disable();
|
||||
@@ -654,17 +630,6 @@ bool NPC::Process()
|
||||
}
|
||||
|
||||
if (tic_timer.Check()) {
|
||||
if (RuleB(Zone, StateSavingOnShutdown) && IsQueuedForCorpse()) {
|
||||
auto decay_timer = m_corpse_decay_time;
|
||||
uint16 corpse_id = GetID();
|
||||
Death(this, GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
||||
auto c = entity_list.GetCorpseByID(corpse_id);
|
||||
if (c) {
|
||||
c->UnLock();
|
||||
c->SetDecayTimer(decay_timer);
|
||||
}
|
||||
}
|
||||
|
||||
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_TICK)) {
|
||||
parse->EventNPC(EVENT_TICK, this, nullptr, "", 0);
|
||||
}
|
||||
|
||||
+6
-10
@@ -603,10 +603,9 @@ public:
|
||||
|
||||
// zone state save
|
||||
inline void SetQueuedToCorpse() { m_queued_for_corpse = true; }
|
||||
inline bool IsQueuedForCorpse() { return m_queued_for_corpse; }
|
||||
inline uint32_t SetCorpseDecayTime(uint32_t decay_time) { return m_corpse_decay_time = decay_time; }
|
||||
inline bool IsQueuedForCorpse() const { return m_queued_for_corpse; }
|
||||
inline void SetResumedFromZoneSuspend(bool state = true) { m_resumed_from_zone_suspend = state; }
|
||||
inline bool IsResumedFromZoneSuspend() { return m_resumed_from_zone_suspend; }
|
||||
inline bool IsResumedFromZoneSuspend() const { return m_resumed_from_zone_suspend; }
|
||||
|
||||
inline void LoadBuffsFromState(std::vector<Buffs_Struct> in_buffs) {
|
||||
int i = 0;
|
||||
@@ -658,15 +657,12 @@ protected:
|
||||
LootItems m_loot_items;
|
||||
|
||||
// zone state
|
||||
bool m_resumed_from_zone_suspend = false;
|
||||
bool m_queued_for_corpse = false; // this is to check for corpse creation on zone state restore
|
||||
uint32_t m_corpse_decay_time = 0; // decay time set on zone state restore
|
||||
Timer m_corpse_queue_timer = {}; // this is to check for corpse creation on zone state restore
|
||||
Timer m_corpse_queue_shutoff_timer = {};
|
||||
bool m_resumed_from_zone_suspend = false;
|
||||
bool m_queued_for_corpse = false; // this is to check for corpse creation on zone state restore
|
||||
|
||||
// this is a 30-second timer that protects a NPC from having double assignment of loot
|
||||
// this is a timer that protects a NPC from having double assignment of loot
|
||||
// this is to prevent a player from killing a NPC and then zoning out and back in to get loot again
|
||||
// if loot was to be assigned via script again, this protects double assignment for 30 seconds
|
||||
// if loot was to be assigned via script again, this protects double assignment for a short time
|
||||
Timer m_resumed_from_zone_suspend_shutoff_timer = {};
|
||||
|
||||
std::list<NpcFactionEntriesRepository::NpcFactionEntries> faction_list;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -939,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(),
|
||||
@@ -959,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
|
||||
};
|
||||
|
||||
+3
-3
@@ -1310,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,
|
||||
@@ -3507,6 +3506,7 @@ void QuestManager::UpdateInstanceTimer(uint16 instance_id, uint32 new_duration)
|
||||
|
||||
e.duration = new_duration;
|
||||
e.start_time = std::time(nullptr);
|
||||
e.expire_at = e.start_time + e.duration;
|
||||
|
||||
const int updated = InstanceListRepository::UpdateOne(database, e);
|
||||
|
||||
|
||||
+16
-5
@@ -191,16 +191,20 @@ bool Spawn2::Process() {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16 condition_value=1;
|
||||
|
||||
uint16 condition_value = 1;
|
||||
if (condition_id > 0) {
|
||||
condition_value = zone->spawn_conditions.GetCondition(zone->GetShortName(), zone->GetInstanceID(), condition_id);
|
||||
condition_value = zone->spawn_conditions.GetCondition(
|
||||
zone->GetShortName(),
|
||||
zone->GetInstanceID(),
|
||||
condition_id
|
||||
);
|
||||
}
|
||||
|
||||
//have the spawn group pick an NPC for us
|
||||
uint32 npcid = 0;
|
||||
if (RuleB(Zone, StateSavingOnShutdown) && currentnpcid && currentnpcid > 0) {
|
||||
npcid = currentnpcid;
|
||||
if (m_resumed_npc_id > 0) {
|
||||
npcid = m_resumed_npc_id;
|
||||
m_resumed_npc_id = 0;
|
||||
} else {
|
||||
npcid = spawn_group->GetNPCType(condition_value);
|
||||
}
|
||||
@@ -277,6 +281,13 @@ 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;
|
||||
|
||||
|
||||
@@ -77,6 +77,8 @@ public:
|
||||
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; }
|
||||
inline void SetResumedNPCID(uint32 npc_id) { m_resumed_npc_id = npc_id; }
|
||||
|
||||
protected:
|
||||
friend class Zone;
|
||||
@@ -104,6 +106,8 @@ private:
|
||||
bool IsDespawned;
|
||||
uint32 killcount;
|
||||
bool m_resumed_from_zone_suspend = false;
|
||||
uint32 m_resumed_npc_id = 0;
|
||||
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);
|
||||
}
|
||||
|
||||
+18
-1
@@ -81,7 +81,6 @@ WorldServer::WorldServer()
|
||||
cur_groupid = 0;
|
||||
last_groupid = 0;
|
||||
oocmuted = false;
|
||||
m_process_timer = std::make_unique<EQ::Timer>(1000, true, std::bind(&WorldServer::Process, this));
|
||||
}
|
||||
|
||||
WorldServer::~WorldServer() {
|
||||
@@ -95,6 +94,7 @@ void WorldServer::Process()
|
||||
if (it->second.reload_at_unix < std::time(nullptr)) {
|
||||
ProcessReload(it->second);
|
||||
it = m_reload_queue.erase(it);
|
||||
break;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
@@ -3786,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;
|
||||
@@ -3981,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;
|
||||
@@ -4586,6 +4599,10 @@ void WorldServer::ProcessReload(const ServerReload::Request& request)
|
||||
zone->ReloadLootTables();
|
||||
break;
|
||||
|
||||
case ServerReload::Type::Maps:
|
||||
zone->ReloadMaps();
|
||||
break;
|
||||
|
||||
case ServerReload::Type::Merchants:
|
||||
entity_list.ReloadMerchants();
|
||||
break;
|
||||
|
||||
@@ -81,7 +81,6 @@ private:
|
||||
ZoneEventScheduler *m_zone_scheduler;
|
||||
|
||||
// server reload queue
|
||||
std::unique_ptr<EQ::Timer> m_process_timer;
|
||||
std::mutex m_reload_mutex = {};
|
||||
std::map<int, ServerReload::Request> m_reload_queue = {};
|
||||
public:
|
||||
|
||||
+8
-6
@@ -887,10 +887,7 @@ void Zone::Shutdown(bool quiet)
|
||||
c.second->WorldKick();
|
||||
}
|
||||
|
||||
bool does_zone_have_entities =
|
||||
zone && zone->IsLoaded() &&
|
||||
(!entity_list.GetNPCList().empty() || !entity_list.GetCorpseList().empty());
|
||||
if (RuleB(Zone, StateSavingOnShutdown) && does_zone_have_entities) {
|
||||
if (RuleB(Zone, StateSavingOnShutdown) && zone && zone->IsLoaded()) {
|
||||
SaveZoneState();
|
||||
}
|
||||
|
||||
@@ -1537,7 +1534,6 @@ bool Zone::Process() {
|
||||
spawn_conditions.Process();
|
||||
|
||||
if (spawn2_timer.Check()) {
|
||||
|
||||
LinkedListIterator<Spawn2 *> iterator(spawn2_list);
|
||||
|
||||
EQ::InventoryProfile::CleanDirty();
|
||||
@@ -3288,5 +3284,11 @@ bool Zone::VariableExists(const std::string& variable_name)
|
||||
return m_zone_variables.find(variable_name) != m_zone_variables.end();
|
||||
}
|
||||
|
||||
#include "zone_save_state.cpp"
|
||||
void Zone::ReloadMaps()
|
||||
{
|
||||
zonemap = Map::LoadMapFile(map_name);
|
||||
watermap = WaterMap::LoadWaterMapfile(map_name);
|
||||
pathing = IPathfinder::Load(map_name);
|
||||
}
|
||||
|
||||
#include "zone_loot.cpp"
|
||||
|
||||
@@ -479,6 +479,7 @@ public:
|
||||
);
|
||||
void SaveZoneState();
|
||||
static void ClearZoneState(uint32 zone_id, uint32 instance_id);
|
||||
void ReloadMaps();
|
||||
|
||||
private:
|
||||
bool allow_mercs;
|
||||
|
||||
+14
-8
@@ -29,17 +29,23 @@ void ZoneCLI::CommandHandler(int argc, char **argv)
|
||||
auto function_map = EQEmuCommand::function_map;
|
||||
|
||||
// Register commands
|
||||
function_map["benchmark:databuckets"] = &ZoneCLI::BenchmarkDatabuckets;
|
||||
function_map["sidecar:serve-http"] = &ZoneCLI::SidecarServeHttp;
|
||||
function_map["tests:databuckets"] = &ZoneCLI::DataBuckets;
|
||||
function_map["tests:npc-handins"] = &ZoneCLI::NpcHandins;
|
||||
function_map["tests:npc-handins-multiquest"] = &ZoneCLI::NpcHandinsMultiQuest;
|
||||
function_map["benchmark:databuckets"] = &ZoneCLI::BenchmarkDatabuckets;
|
||||
function_map["sidecar:serve-http"] = &ZoneCLI::SidecarServeHttp;
|
||||
function_map["tests:databuckets"] = &ZoneCLI::TestDataBuckets;
|
||||
function_map["tests:npc-handins"] = &ZoneCLI::TestNpcHandins;
|
||||
function_map["tests:npc-handins-multiquest"] = &ZoneCLI::TestNpcHandinsMultiQuest;
|
||||
function_map["tests:zone-state"] = &ZoneCLI::TestZoneState;
|
||||
|
||||
EQEmuCommand::HandleMenu(function_map, cmd, argc, argv);
|
||||
}
|
||||
|
||||
#include "cli/databuckets.cpp"
|
||||
// cli
|
||||
#include "cli/benchmark_databuckets.cpp"
|
||||
#include "cli/sidecar_serve_http.cpp"
|
||||
#include "cli/npc_handins.cpp"
|
||||
#include "cli/npc_handins_multiquest.cpp"
|
||||
|
||||
// tests
|
||||
#include "cli/tests/_test_util.cpp"
|
||||
#include "cli/tests/databuckets.cpp"
|
||||
#include "cli/tests/npc_handins.cpp"
|
||||
#include "cli/tests/npc_handins_multiquest.cpp"
|
||||
#include "cli/tests/zone_state.cpp"
|
||||
|
||||
+5
-4
@@ -1,6 +1,7 @@
|
||||
#ifndef EQEMU_ZONE_CLI_H
|
||||
#define EQEMU_ZONE_CLI_H
|
||||
|
||||
#include <iostream>
|
||||
#include "../common/cli/argh.h"
|
||||
|
||||
class ZoneCLI {
|
||||
@@ -11,10 +12,10 @@ public:
|
||||
static bool RanConsoleCommand(int argc, char **argv);
|
||||
static bool RanSidecarCommand(int argc, char **argv);
|
||||
static bool RanTestCommand(int argc, char **argv);
|
||||
static void DataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||
static void NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||
static void NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||
static void TestDataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||
static void TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||
static void TestNpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||
static void TestZoneState(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||
};
|
||||
|
||||
|
||||
#endif //EQEMU_ZONE_CLI_H
|
||||
|
||||
+8
-4
@@ -368,18 +368,22 @@ void Zone::LoadLootDrops(const std::vector<uint32> in_lootdrop_ids)
|
||||
m_lootdrops.emplace_back(e);
|
||||
|
||||
// add lootdrop entries
|
||||
for (const auto &f: lootdrop_entries) {
|
||||
if (e.id == f.lootdrop_id) {
|
||||
// add lootdrop entries
|
||||
for (const auto &h: lootdrop_entries) {
|
||||
if (e.id == h.lootdrop_id) {
|
||||
|
||||
// check if lootdrop entry already exists in memory
|
||||
has_entry = false;
|
||||
for (const auto &g: m_lootdrop_entries) {
|
||||
if (f.lootdrop_id == g.lootdrop_id && f.item_id == g.item_id && f.multiplier == g.multiplier) {
|
||||
for (const auto &i: m_lootdrop_entries) {
|
||||
if (h.lootdrop_id == i.lootdrop_id && h.item_id == i.item_id) {
|
||||
has_entry = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_entry) {
|
||||
m_lootdrop_entries.emplace_back(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+76
-85
@@ -4,46 +4,7 @@
|
||||
#include "npc.h"
|
||||
#include "corpse.h"
|
||||
#include "zone.h"
|
||||
#include "../common/repositories/zone_state_spawns_repository.h"
|
||||
#include "../common/repositories/spawn2_disabled_repository.h"
|
||||
|
||||
struct LootEntryStateData {
|
||||
uint32 item_id;
|
||||
uint32_t lootdrop_id;
|
||||
uint16 charges = 0; // used in dynamically added loot (AddItem)
|
||||
|
||||
// cereal
|
||||
template<class Archive>
|
||||
void serialize(Archive &ar)
|
||||
{
|
||||
ar(
|
||||
CEREAL_NVP(item_id),
|
||||
CEREAL_NVP(lootdrop_id),
|
||||
CEREAL_NVP(charges)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
struct LootStateData {
|
||||
uint32 copper = 0;
|
||||
uint32 silver = 0;
|
||||
uint32 gold = 0;
|
||||
uint32 platinum = 0;
|
||||
std::vector<LootEntryStateData> entries = {};
|
||||
|
||||
// cereal
|
||||
template<class Archive>
|
||||
void serialize(Archive &ar)
|
||||
{
|
||||
ar(
|
||||
CEREAL_NVP(copper),
|
||||
CEREAL_NVP(silver),
|
||||
CEREAL_NVP(gold),
|
||||
CEREAL_NVP(platinum),
|
||||
CEREAL_NVP(entries)
|
||||
);
|
||||
}
|
||||
};
|
||||
#include "zone_save_state.h"
|
||||
|
||||
// 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
|
||||
@@ -204,6 +165,33 @@ 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)) {
|
||||
@@ -214,25 +202,7 @@ inline void LoadNPCEntityVariables(NPC *n, const std::string &entity_variables)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Strings::IsValidJson(entity_variables)) {
|
||||
LogZoneState("Invalid JSON data for NPC [{}]", n->GetNPCTypeID());
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -310,11 +280,32 @@ 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 these are zero for some reason, we need to reset the max hp
|
||||
if (!s.is_corpse) {
|
||||
if (s.hp == 0 || n->GetHP() == 0) {
|
||||
n->SetMaxHP();
|
||||
}
|
||||
if (s.mana == 0 || n->GetMana() == 0) {
|
||||
n->RestoreMana();
|
||||
}
|
||||
}
|
||||
|
||||
if (s.grid) {
|
||||
n->AssignWaypoints(s.grid, s.current_waypoint);
|
||||
@@ -323,20 +314,21 @@ inline void LoadNPCState(Zone *zone, NPC *n, ZoneStateSpawnsRepository::ZoneStat
|
||||
n->SetResumedFromZoneSuspend(false);
|
||||
LoadLootStateData(zone, n, s.loot_data);
|
||||
n->SetResumedFromZoneSuspend(true);
|
||||
LoadNPCEntityVariables(n, s.entity_variables);
|
||||
LoadNPCBuffs(n, s.buffs);
|
||||
|
||||
if (s.is_corpse) {
|
||||
auto decay_time = s.decay_in_seconds * 1000;
|
||||
if (decay_time > 0) {
|
||||
n->SetQueuedToCorpse();
|
||||
n->SetCorpseDecayTime(decay_time);
|
||||
entity_list.RestoreCorpse(n, decay_time);
|
||||
}
|
||||
else {
|
||||
n->Depop();
|
||||
}
|
||||
}
|
||||
|
||||
n->SetPosition(s.x, s.y, s.z);
|
||||
n->SetHeading(s.heading);
|
||||
n->SetResumedFromZoneSuspend(true);
|
||||
}
|
||||
|
||||
@@ -422,12 +414,17 @@ bool Zone::LoadZoneState(
|
||||
zone->initgrids_timer.Trigger();
|
||||
zone->Process();
|
||||
|
||||
// load zone variables first
|
||||
int count = 0;
|
||||
for (auto &s: spawn_states) {
|
||||
if (s.is_zone) {
|
||||
LoadZoneVariables(zone, s.entity_variables);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// spawn2
|
||||
for (auto &s: spawn_states) {
|
||||
if (s.spawngroup_id == 0 || s.is_corpse || s.is_zone) {
|
||||
continue;
|
||||
}
|
||||
@@ -467,10 +464,13 @@ bool Zone::LoadZoneState(
|
||||
);
|
||||
|
||||
if (spawn_time_left == 0) {
|
||||
new_spawn->SetCurrentNPCID(s.npc_id);
|
||||
new_spawn->SetResumedNPCID(s.npc_id);
|
||||
new_spawn->SetResumedFromZoneSuspend(true);
|
||||
new_spawn->SetEntityVariables(GetVariablesDeserialized(s.entity_variables));
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
spawn2_list.Insert(new_spawn);
|
||||
new_spawn->Process();
|
||||
auto n = new_spawn->GetNPC();
|
||||
@@ -505,29 +505,13 @@ bool Zone::LoadZoneState(
|
||||
npc->SetQueuedToCorpse();
|
||||
}
|
||||
|
||||
LoadNPCStatePreSpawn(zone, npc, s);
|
||||
|
||||
entity_list.AddNPC(npc, true, true);
|
||||
|
||||
LoadNPCState(zone, npc, s);
|
||||
}
|
||||
|
||||
// any NPC that is spawned by the spawn system
|
||||
for (auto &e: entity_list.GetNPCList()) {
|
||||
auto npc = e.second;
|
||||
if (npc->GetSpawnGroupId() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto &s: spawn_states) {
|
||||
bool is_same_npc =
|
||||
s.npc_id == npc->GetNPCTypeID() &&
|
||||
s.spawn2_id == npc->GetSpawnPointID() &&
|
||||
s.spawngroup_id == npc->GetSpawnGroupId();
|
||||
if (is_same_npc) {
|
||||
LoadNPCState(zone, npc, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !spawn_states.empty();
|
||||
}
|
||||
|
||||
@@ -558,7 +542,7 @@ inline void SaveNPCState(NPC *n, ZoneStateSpawnsRepository::ZoneStateSpawns &s)
|
||||
auto buffs = n->GetBuffs();
|
||||
if (buffs) {
|
||||
std::vector<Buffs_Struct> valid_buffs;
|
||||
for (int index = 0; index < n->GetMaxBuffSlots(); index++) {
|
||||
for (int index = 0; index < n->GetMaxBuffSlots(); index++) {
|
||||
if (buffs[index].spellid != 0 && buffs[index].spellid != 65535) {
|
||||
valid_buffs.push_back(buffs[index]);
|
||||
}
|
||||
@@ -600,6 +584,7 @@ void Zone::SaveZoneState()
|
||||
std::vector<ZoneStateSpawnsRepository::ZoneStateSpawns> spawns = {};
|
||||
LinkedListIterator<Spawn2 *> iterator(spawn2_list);
|
||||
iterator.Reset();
|
||||
int count = 0;
|
||||
while (iterator.MoreElements()) {
|
||||
Spawn2 *sp = iterator.GetData();
|
||||
auto s = ZoneStateSpawnsRepository::NewEntity();
|
||||
@@ -629,6 +614,7 @@ void Zone::SaveZoneState()
|
||||
|
||||
spawns.emplace_back(s);
|
||||
iterator.Advance();
|
||||
count++;
|
||||
}
|
||||
|
||||
// npc's that are not in the spawn2 list
|
||||
@@ -704,6 +690,11 @@ void Zone::SaveZoneState()
|
||||
return;
|
||||
}
|
||||
|
||||
if (spawns.empty()) {
|
||||
LogInfo("No zone state data to save");
|
||||
return;
|
||||
}
|
||||
|
||||
ZoneStateSpawnsRepository::InsertMany(database, spawns);
|
||||
|
||||
LogInfo("Saved [{}] zone state spawns", Strings::Commify(spawns.size()));
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cereal/archives/json.hpp>
|
||||
#include <cereal/types/map.hpp>
|
||||
#include "npc.h"
|
||||
#include "corpse.h"
|
||||
#include "zone.h"
|
||||
#include "../common/repositories/zone_state_spawns_repository.h"
|
||||
#include "../common/repositories/spawn2_disabled_repository.h"
|
||||
|
||||
struct LootEntryStateData {
|
||||
uint32 item_id = 0;
|
||||
uint32_t lootdrop_id = 0;
|
||||
uint16 charges = 0; // used in dynamically added loot (AddItem)
|
||||
|
||||
// cereal
|
||||
template<class Archive>
|
||||
void serialize(Archive &ar)
|
||||
{
|
||||
ar(
|
||||
CEREAL_NVP(item_id),
|
||||
CEREAL_NVP(lootdrop_id),
|
||||
CEREAL_NVP(charges)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
struct LootStateData {
|
||||
uint32 copper = 0;
|
||||
uint32 silver = 0;
|
||||
uint32 gold = 0;
|
||||
uint32 platinum = 0;
|
||||
std::vector<LootEntryStateData> entries = {};
|
||||
|
||||
// cereal
|
||||
template<class Archive>
|
||||
void serialize(Archive &ar)
|
||||
{
|
||||
ar(
|
||||
CEREAL_NVP(copper),
|
||||
CEREAL_NVP(silver),
|
||||
CEREAL_NVP(gold),
|
||||
CEREAL_NVP(platinum),
|
||||
CEREAL_NVP(entries)
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -164,6 +164,7 @@ void ZoneDatabase::UpdateRespawnTime(uint32 spawn2_id, uint16 instance_id, uint3
|
||||
.id = static_cast<int32_t>(spawn2_id),
|
||||
.start = static_cast<int32_t>(current_time),
|
||||
.duration = static_cast<int32_t>(time_left),
|
||||
.expire_at = current_time + time_left,
|
||||
.instance_id = static_cast<int16_t>(instance_id)
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user