Compare commits

..

1 Commits

Author SHA1 Message Date
Alex King 12eb838ee9 [Bug Fix] Fix Scripted Spawns from spawning before Variables are loaded. 2025-03-22 12:57:22 -04:00
112 changed files with 1484 additions and 3768 deletions
-151
View File
@@ -1,154 +1,3 @@
## [23.5.0] 4/10/2025
### API
* World API Optimizations ([#4850](https://github.com/EQEmu/Server/pull/4850)) @Akkadius 2025-04-10
### Bots
* Add valid state checks to ^clickitem ([#4830](https://github.com/EQEmu/Server/pull/4830)) @nytmyr 2025-04-10
* Flag all buffs with SE_DamageShield as Damage Shield ([#4833](https://github.com/EQEmu/Server/pull/4833)) @nytmyr 2025-04-10
* Positioning rewrite ([#4856](https://github.com/EQEmu/Server/pull/4856)) @nytmyr 2025-04-10
* Restore old buff overwrite blocking ([#4832](https://github.com/EQEmu/Server/pull/4832)) @nytmyr 2025-04-10
### Bugfix
* Load zone variables before encounter_load. ([#4846](https://github.com/EQEmu/Server/pull/4846)) @zimp-wow 2025-04-10
* Prevent depops from blocking new spawns. ([#4841](https://github.com/EQEmu/Server/pull/4841)) @zimp-wow 2025-04-10
* Prevent final shutdown from persisting incomplete state. ([#4849](https://github.com/EQEmu/Server/pull/4849)) @zimp-wow 2025-04-10
### Code
* Remove queryserv dump flag ([#4842](https://github.com/EQEmu/Server/pull/4842)) @joligario 2025-04-10
* Update link for legacy EQEmu loginserver account setup ([#4826](https://github.com/EQEmu/Server/pull/4826)) @joligario 2025-04-10
### Crash
* Fix rarer exception crash issue in PlayerEventLogs::ProcessBatchQueue ([#4835](https://github.com/EQEmu/Server/pull/4835)) @Akkadius 2025-04-03
### Database
* Fix manifest for `helmtexture` in `horses` table ([#4852](https://github.com/EQEmu/Server/pull/4852)) @joligario 2025-04-10
### Feature
* Add rule to consume command text from any channel ([#4839](https://github.com/EQEmu/Server/pull/4839)) @catapultam-habeo 2025-04-10
### Fixes
* Add the bazaar search limit to query ([#4829](https://github.com/EQEmu/Server/pull/4829)) @neckkola 2025-04-10
* Backfill expire_at (not sure why this didn't make it in there to begin with) @Akkadius 2025-03-31
* Bazaar Search window not working in a DZ ([#4828](https://github.com/EQEmu/Server/pull/4828)) @neckkola 2025-04-10
* Databuckets Account Cache Loading ([#4855](https://github.com/EQEmu/Server/pull/4855)) @Akkadius 2025-04-10
* Fix missing timer_name check on Mob::StopTimer ([#4840](https://github.com/EQEmu/Server/pull/4840)) @zimp-wow 2025-04-04
* FixHeading Infinite Loop Fix ([#4854](https://github.com/EQEmu/Server/pull/4854)) @KimLS 2025-04-10
* Make sure we don't expire default value instances @Akkadius 2025-03-31
* Regression in World SendEmoteMessageRaw ([#4837](https://github.com/EQEmu/Server/pull/4837)) @Akkadius 2025-04-03
* Remove QS Tables From Export @Akkadius 2025-04-10
* Zone State Spawn2 Location Restore ([#4844](https://github.com/EQEmu/Server/pull/4844)) @Akkadius 2025-04-10
### Netcode
* Fix Stale Client Edge Case ([#4853](https://github.com/EQEmu/Server/pull/4853)) @Akkadius 2025-04-10
### Performance
* Character Save Optimizations ([#4851](https://github.com/EQEmu/Server/pull/4851)) @Akkadius 2025-04-10
* Network Ring Buffers ([#4857](https://github.com/EQEmu/Server/pull/4857)) @Akkadius 2025-04-10
* Pre-Compute CLE Server Lists ([#4838](https://github.com/EQEmu/Server/pull/4838)) @Akkadius 2025-04-10
### Spells
* Fear resistance effects edge case fixes and support for SPA 102 as an AA ([#4848](https://github.com/EQEmu/Server/pull/4848)) @KayenEQ 2025-04-10
* Update to SPA 180 SE_ResistSpellChance to not block unresistable spells. ([#4847](https://github.com/EQEmu/Server/pull/4847)) @KayenEQ 2025-04-10
* Update to SPA 378 SE_SpellEffectResistChance ([#4845](https://github.com/EQEmu/Server/pull/4845)) @KayenEQ 2025-04-10
## [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
-4
View File
@@ -671,7 +671,6 @@ SET(common_headers
net/console_server_connection.h
net/crc32.h
net/daybreak_connection.h
net/daybreak_pooling.h
net/daybreak_structs.h
net/dns.h
net/endian.h
@@ -683,7 +682,6 @@ SET(common_headers
net/servertalk_server.h
net/servertalk_server_connection.h
net/tcp_connection.h
net/tcp_connection_pooling.h
net/tcp_server.h
net/websocket_server.h
net/websocket_server_connection.h
@@ -744,7 +742,6 @@ SOURCE_GROUP(Net FILES
net/crc32.h
net/daybreak_connection.cpp
net/daybreak_connection.h
net/daybreak_pooling.h
net/daybreak_structs.h
net/dns.h
net/endian.h
@@ -765,7 +762,6 @@ SOURCE_GROUP(Net FILES
net/servertalk_server_connection.h
net/tcp_connection.cpp
net/tcp_connection.h
net/tcp_connection_pooling.h
net/tcp_server.cpp
net/tcp_server.h
net/websocket_server.cpp
+1 -2
View File
@@ -279,8 +279,7 @@ Bazaar::GetSearchResults(
trader_items_ids,
std::string(search.item_name),
field_criteria_items,
where_criteria_items,
search.max_results
where_criteria_items
);
if (item_results.empty()) {
-1
View File
@@ -141,7 +141,6 @@ 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);
+1
View File
@@ -65,6 +65,7 @@ private:
bool dump_system_tables = false;
bool dump_content_tables = false;
bool dump_player_tables = false;
bool dump_query_server_tables = false;
bool dump_login_server_tables = false;
bool dump_with_no_data = false;
bool dump_table_lock = false;
+5 -91
View File
@@ -6792,7 +6792,7 @@ UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 341) + 5810)
},
ManifestEntry{
.version = 9304,
.description = "2024_12_01_update_guild_bank",
.description = "2024_12_01_2024_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_03_29_add_multivalue_support_to_evolving_subtype.sql",
.description = "2025_add_multivalue_support_to_evolving_subtype.sql",
.check = "SHOW COLUMNS FROM `items_evolving_details` LIKE 'sub_type'",
.condition = "missing",
.match = "varchar(200)",
@@ -6942,8 +6942,8 @@ CREATE TABLE `character_pet_name` (
.version = 9310,
.description = "2025_03_7_expand_horse_def.sql",
.check = "SHOW COLUMNS FROM `horses` LIKE 'helmtexture'",
.condition = "empty",
.match = "",
.condition = "missing",
.match = "TINYINT(2)",
.sql = R"(
ALTER TABLE `horses`
ADD COLUMN `helmtexture` TINYINT(2) NOT NULL DEFAULT -1 AFTER `texture`;
@@ -6986,6 +6986,7 @@ 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
},
@@ -6997,93 +6998,6 @@ ALTER TABLE zone_state_spawns ADD INDEX idx_zone_instance (zone_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
},
+18 -45
View File
@@ -128,35 +128,11 @@ 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;
RespawnTimesRepository::ClearInstanceTimers(*this, e.id);
InstanceListRepository::ReplaceOne(*this, e);
return instance_id > 0 && e.id;
return InstanceListRepository::InsertOne(*this, 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;
@@ -561,12 +537,14 @@ 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,
fmt::format(
"expire_at <= (UNIX_TIMESTAMP() - {}) and expire_at != 0 AND never_expires = 0",
RuleI(Instances, ExpireOffsetTimeSeconds)
)
"(start_time + duration) <= (UNIX_TIMESTAMP() - 86400) AND never_expires = 0"
);
if (l.empty()) {
return;
@@ -577,24 +555,20 @@ void Database::PurgeExpiredInstances()
instance_ids.emplace_back(std::to_string(e.id));
}
const auto ids = Strings::Implode(",", instance_ids);
const auto imploded_instance_ids = Strings::Implode(",", 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));
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));
if (RuleB(Zone, StateSavingOnShutdown)) {
ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` IN ({})", ids));
ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` IN ({})", imploded_instance_ids));
}
TransactionCommit();
LogInfo("Purged [{}] expired instances", l.size());
}
void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
@@ -606,7 +580,6 @@ 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);
}
+3 -4
View File
@@ -58,16 +58,15 @@ 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::ReplaceOne(GetDatabase(), insert_instance);
if (!instance)
auto instance = InstanceListRepository::InsertOne(GetDatabase(), insert_instance);
if (instance.id == 0)
{
LogDynamicZones("Failed to create instance [{}] for zone [{}]", unused_instance_id, m_zone_id);
return 0;
}
m_instance_id = unused_instance_id;
m_instance_id = instance.id;
return m_instance_id;
}
+3 -7
View File
@@ -74,7 +74,7 @@ namespace Logs {
Spawns,
Spells,
Status, // deprecated
TCPConnection, // deprecated
TCPConnection,
Tasks,
Tradeskills,
Trading,
@@ -150,8 +150,6 @@ namespace Logs {
BotSpellTypeChecks,
NpcHandin,
ZoneState,
NetClient,
NetTCP,
MaxCategoryID /* Don't Remove this */
};
@@ -185,7 +183,7 @@ namespace Logs {
"Spawns",
"Spells",
"Status (Deprecated)",
"TCP Connection (Deprecated)",
"TCP Connection",
"Tasks",
"Tradeskills",
"Trading",
@@ -260,9 +258,7 @@ namespace Logs {
"Bot Spell Checks",
"Bot Spell Type Checks",
"NpcHandin",
"ZoneState",
"Net Server <-> Client",
"Net TCP"
"ZoneState"
};
}
+20 -20
View File
@@ -261,6 +261,26 @@
OutF(LogSys, Logs::Detail, Logs::Spells, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogStatus(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::Status))\
OutF(LogSys, Logs::General, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogStatusDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::Status))\
OutF(LogSys, Logs::Detail, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogTCPConnection(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::TCPConnection))\
OutF(LogSys, Logs::General, Logs::TCPConnection, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogTCPConnectionDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::TCPConnection))\
OutF(LogSys, Logs::Detail, Logs::TCPConnection, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogTasks(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::Tasks))\
OutF(LogSys, Logs::General, Logs::Tasks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
@@ -904,26 +924,6 @@
OutF(LogSys, Logs::Detail, Logs::ZoneState, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogNetClient(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::NetClient))\
OutF(LogSys, Logs::General, Logs::NetClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogNetClientDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::NetClient))\
OutF(LogSys, Logs::Detail, Logs::NetClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogNetTCP(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::NetTCP))\
OutF(LogSys, Logs::General, Logs::NetTCP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogNetTCPDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::NetTCP))\
OutF(LogSys, Logs::Detail, Logs::NetTCP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\
if (LogSys.IsLogEnabled(debug_level, log_category))\
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
+4 -12
View File
@@ -81,7 +81,7 @@ void PlayerEventLogs::Init()
if (!settings_to_insert.empty()) {
PlayerEventLogSettingsRepository::ReplaceMany(*m_database, settings_to_insert);
}
bool processing_in_world = !RuleB(Logging, PlayerEventsQSProcess) && IsWorld();
bool processing_in_qs = RuleB(Logging, PlayerEventsQSProcess) && IsQueryServ();
@@ -181,17 +181,9 @@ void PlayerEventLogs::ProcessBatchQueue()
// Helper to deserialize event data
auto Deserialize = [](const std::string &data, auto &out) {
if (!Strings::IsValidJson(data)) {
return;
}
// cpp exceptions are terrible, don't ever use them
try {
std::stringstream ss(data);
cereal::JSONInputArchive ar(ss);
out.serialize(ar);
}
catch (const std::exception &e) {}
std::stringstream ss(data);
cereal::JSONInputArchive ar(ss);
out.serialize(ar);
};
// Helper to assign ETL table ID
+8 -16
View File
@@ -906,32 +906,24 @@ 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 (RuleI(World, FVNoDropFlag) == FVNoDropFlagRule::Enabled && m_item->FVNoDrop == 0) {
return true;
}
if (m_item->NoDrop == 0) {
/*if (m_item->FVNoDrop != 0) // not implemented
return false;*/
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;
}
}
}
+97 -105
View File
@@ -1,16 +1,12 @@
#include "daybreak_connection.h"
#include "../event/event_loop.h"
#include "../event/task.h"
#include "../data_verification.h"
#include "crc32.h"
#include "../eqemu_logsys.h"
#include <zlib.h>
#include <fmt/format.h>
// observed client receive window is 300 packets, 140KB
constexpr size_t MAX_CLIENT_RECV_PACKETS_PER_WINDOW = 300;
constexpr size_t MAX_CLIENT_RECV_BYTES_PER_WINDOW = 140 * 1024;
// buffer pools
SendBufferPool send_buffer_pool;
#include <sstream>
EQ::Net::DaybreakConnectionManager::DaybreakConnectionManager()
{
@@ -57,22 +53,16 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop)
uv_ip4_addr("0.0.0.0", m_options.port, &recv_addr);
int rc = uv_udp_bind(&m_socket, (const struct sockaddr *)&recv_addr, UV_UDP_REUSEADDR);
rc = uv_udp_recv_start(
&m_socket,
[](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
if (suggested_size > 65536) {
buf->base = new char[suggested_size];
buf->len = suggested_size;
return;
}
static thread_local char temp_buf[65536];
buf->base = temp_buf;
buf->len = 65536;
},
rc = uv_udp_recv_start(&m_socket,
[](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
buf->base = new char[suggested_size];
memset(buf->base, 0, suggested_size);
buf->len = suggested_size;
},
[](uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) {
DaybreakConnectionManager *c = (DaybreakConnectionManager*)handle->data;
if (nread < 0 || addr == nullptr) {
delete[] buf->base;
return;
}
@@ -80,10 +70,7 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop)
uv_ip4_name((const sockaddr_in*)addr, endpoint, 16);
auto port = ntohs(((const sockaddr_in*)addr)->sin_port);
c->ProcessPacket(endpoint, port, buf->base, nread);
if (buf->len > 65536) {
delete[] buf->base;
}
delete[] buf->base;
});
m_attached = loop;
@@ -323,7 +310,7 @@ EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner
m_last_session_stats = Clock::now();
m_outgoing_budget = owner->m_options.outgoing_data_rate;
LogNetClient("New session [{}] with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
LogNetcode("New session [{}] with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
}
//new connection made as client
@@ -355,16 +342,16 @@ EQ::Net::DaybreakConnection::~DaybreakConnection()
void EQ::Net::DaybreakConnection::Close()
{
if (m_status != StatusDisconnected && m_status != StatusDisconnecting) {
if (m_status == StatusConnected) {
FlushBuffer();
SendDisconnect();
}
if (m_status != StatusDisconnecting) {
m_close_time = Clock::now();
ChangeStatus(StatusDisconnecting);
}
else {
ChangeStatus(StatusDisconnecting);
}
ChangeStatus(StatusDisconnecting);
}
void EQ::Net::DaybreakConnection::QueuePacket(Packet &p)
@@ -647,7 +634,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
p.PutSerialize(0, reply);
InternalSend(p);
LogNetClient("[OP_SessionRequest] Session [{}] started with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
LogNetcode("[OP_SessionRequest] Session [{}] started with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
}
break;
@@ -666,7 +653,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
m_max_packet_size = reply.max_packet_size;
ChangeStatus(StatusConnected);
LogNetClient(
LogNetcode(
"[OP_SessionResponse] Session [{}] refresh with encode key [{}]",
m_connect_code,
HostToNetwork(m_encode_key)
@@ -795,7 +782,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
SendDisconnect();
}
LogNetClient(
LogNetcode(
"[OP_SessionDisconnect] Session [{}] disconnect with encode key [{}]",
m_connect_code,
HostToNetwork(m_encode_key)
@@ -865,7 +852,7 @@ bool EQ::Net::DaybreakConnection::ValidateCRC(Packet &p)
}
if (p.Length() < (size_t)m_crc_bytes) {
LogNetClient("Session [{}] ignored packet (crc bytes invalid on session)", m_connect_code);
LogNetcode("Session [{}] ignored packet (crc bytes invalid on session)", m_connect_code);
return false;
}
@@ -1056,7 +1043,7 @@ void EQ::Net::DaybreakConnection::Decompress(Packet &p, size_t offset, size_t le
return;
}
static thread_local uint8_t new_buffer[4096];
static uint8_t new_buffer[4096];
uint8_t *buffer = (uint8_t*)p.Data() + offset;
uint32_t new_length = 0;
@@ -1077,7 +1064,7 @@ void EQ::Net::DaybreakConnection::Decompress(Packet &p, size_t offset, size_t le
void EQ::Net::DaybreakConnection::Compress(Packet &p, size_t offset, size_t length)
{
static thread_local uint8_t new_buffer[2048] = { 0 };
uint8_t new_buffer[2048] = { 0 };
uint8_t *buffer = (uint8_t*)p.Data() + offset;
uint32_t new_length = 0;
bool send_uncompressed = true;
@@ -1104,6 +1091,10 @@ void EQ::Net::DaybreakConnection::ProcessResend()
}
}
// observed client receive window is 300 packets, 140KB
constexpr size_t MAX_CLIENT_RECV_PACKETS_PER_WINDOW = 300;
constexpr size_t MAX_CLIENT_RECV_BYTES_PER_WINDOW = 140 * 1024;
void EQ::Net::DaybreakConnection::ProcessResend(int stream)
{
if (m_status == DbProtocolStatus::StatusDisconnected) {
@@ -1126,24 +1117,23 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
auto &first_packet = s->sent_packets.begin()->second;
auto time_since_first_sent = std::chrono::duration_cast<std::chrono::milliseconds>(now - first_packet.first_sent).count();
if (time_since_first_sent >= m_owner->m_options.resend_timeout) {
Close();
return;
}
// make sure that the first_packet in the list first_sent time is within the resend_delay and now
// if it is not, then we need to resend all packets in the list
if (time_since_first_sent <= first_packet.resend_delay && !m_acked_since_last_resend) {
LogNetClient(
"Not resending packets for stream [{}] packets [{}] time_first_sent [{}] resend_delay [{}] m_acked_since_last_resend [{}]",
LogNetcodeDetail(
"Not resending packets for stream [{}] time since first sent [{}] resend delay [{}] m_acked_since_last_resend [{}]",
stream,
s->sent_packets.size(),
time_since_first_sent,
first_packet.resend_delay,
m_acked_since_last_resend
);
return;
}
if (time_since_first_sent >= m_owner->m_options.resend_timeout) {
Close();
return;
}
}
if (LogSys.IsLogEnabled(Logs::Detail, Logs::Netcode)) {
@@ -1152,7 +1142,7 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
total_size += e.second.packet.Length();
}
LogNetClient(
LogNetcodeDetail(
"Resending packets for stream [{}] packet count [{}] total packet size [{}] m_acked_since_last_resend [{}]",
stream,
s->sent_packets.size(),
@@ -1164,7 +1154,7 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
for (auto &e: s->sent_packets) {
if (m_resend_packets_sent >= MAX_CLIENT_RECV_PACKETS_PER_WINDOW ||
m_resend_bytes_sent >= MAX_CLIENT_RECV_BYTES_PER_WINDOW) {
LogNetClient(
LogNetcodeDetail(
"Stopping resend because we hit thresholds m_resend_packets_sent [{}] max [{}] m_resend_bytes_sent [{}] max [{}]",
m_resend_packets_sent,
MAX_CLIENT_RECV_PACKETS_PER_WINDOW,
@@ -1343,97 +1333,99 @@ void EQ::Net::DaybreakConnection::SendKeepAlive()
InternalSend(p);
}
void EQ::Net::DaybreakConnection::InternalSend(Packet &p) {
void EQ::Net::DaybreakConnection::InternalSend(Packet &p)
{
if (m_owner->m_options.outgoing_data_rate > 0.0) {
auto new_budget = m_outgoing_budget - (p.Length() / 1024.0);
if (new_budget <= 0.0) {
m_stats.dropped_datarate_packets++;
return;
} else {
}
else {
m_outgoing_budget = new_budget;
}
}
m_last_send = Clock::now();
auto pooled_opt = send_buffer_pool.acquire();
if (!pooled_opt) {
m_stats.dropped_datarate_packets++;
return;
}
auto [send_req, data, ctx] = *pooled_opt;
ctx->pool = &send_buffer_pool; // set pool pointer
sockaddr_in send_addr{};
uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr);
uv_buf_t send_buffers[1];
auto send_func = [](uv_udp_send_t* req, int status) {
delete[](char*)req->data;
delete req;
};
if (PacketCanBeEncoded(p)) {
m_stats.bytes_before_encode += p.Length();
DynamicPacket out;
out.PutPacket(0, p);
for (auto &m_encode_passe: m_encode_passes) {
switch (m_encode_passe) {
case EncodeCompression:
if (out.GetInt8(0) == 0) {
Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
} else {
Compress(out, 1, out.Length() - 1);
}
break;
case EncodeXOR:
if (out.GetInt8(0) == 0) {
Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
} else {
Encode(out, 1, out.Length() - 1);
}
break;
default:
break;
for (int i = 0; i < 2; ++i) {
switch (m_encode_passes[i]) {
case EncodeCompression:
if (out.GetInt8(0) == 0)
Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
else
Compress(out, 1, out.Length() - 1);
break;
case EncodeXOR:
if (out.GetInt8(0) == 0)
Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
else
Encode(out, 1, out.Length() - 1);
break;
default:
break;
}
}
AppendCRC(out);
uv_udp_send_t *send_req = new uv_udp_send_t;
memset(send_req, 0, sizeof(*send_req));
sockaddr_in send_addr;
uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr);
uv_buf_t send_buffers[1];
char *data = new char[out.Length()];
memcpy(data, out.Data(), out.Length());
send_buffers[0] = uv_buf_init(data, out.Length());
} else {
memcpy(data, p.Data(), p.Length());
send_buffers[0] = uv_buf_init(data, p.Length());
send_req->data = send_buffers[0].base;
m_stats.sent_bytes += out.Length();
m_stats.sent_packets++;
if (m_owner->m_options.simulated_out_packet_loss && m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) {
delete[](char*)send_req->data;
delete send_req;
return;
}
uv_udp_send(send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr*)&send_addr, send_func);
return;
}
m_stats.bytes_before_encode += p.Length();
uv_udp_send_t *send_req = new uv_udp_send_t;
sockaddr_in send_addr;
uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr);
uv_buf_t send_buffers[1];
char *data = new char[p.Length()];
memcpy(data, p.Data(), p.Length());
send_buffers[0] = uv_buf_init(data, p.Length());
send_req->data = send_buffers[0].base;
m_stats.sent_bytes += p.Length();
m_stats.sent_packets++;
if (m_owner->m_options.simulated_out_packet_loss &&
m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) {
send_buffer_pool.release(ctx);
if (m_owner->m_options.simulated_out_packet_loss && m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) {
delete[](char*)send_req->data;
delete send_req;
return;
}
int send_result = uv_udp_send(
send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr *)&send_addr,
[](uv_udp_send_t *req, int status) {
auto *ctx = reinterpret_cast<EmbeddedContext *>(req->data);
if (!ctx) {
std::cerr << "Error: send_req->data is null in callback!" << std::endl;
return;
}
if (status < 0) {
std::cerr << "uv_udp_send failed: " << uv_strerror(status) << std::endl;
}
ctx->pool->release(ctx);
}
);
if (send_result < 0) {
std::cerr << "uv_udp_send() failed: " << uv_strerror(send_result) << std::endl;
send_buffer_pool.release(ctx);
}
uv_udp_send(send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr*)&send_addr, send_func);
}
void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id, bool reliable)
-1
View File
@@ -3,7 +3,6 @@
#include "../random.h"
#include "packet.h"
#include "daybreak_structs.h"
#include "daybreak_pooling.h"
#include <uv.h>
#include <chrono>
#include <functional>
-123
View File
@@ -1,123 +0,0 @@
#pragma once
#include <optional>
#include <atomic>
#include <memory>
#include <array>
#include <vector>
#include <mutex>
#include <iostream>
#include "../eqemu_logsys.h"
#include <uv.h>
constexpr size_t UDP_BUFFER_SIZE = 512;
struct EmbeddedContext {
size_t pool_index;
class SendBufferPool* pool;
};
class SendBufferPool {
public:
explicit SendBufferPool(size_t initial_capacity = 64)
: m_capacity(initial_capacity), m_head(0)
{
LogNetClient("[SendBufferPool] Initializing with capacity [{}]", (int)m_capacity);
m_pool.reserve(m_capacity);
m_locks = std::make_unique<std::atomic_bool[]>(m_capacity);
for (size_t i = 0; i < m_capacity; ++i) {
auto* req = new PooledUdpSend();
req->context.pool_index = i;
req->context.pool = this;
req->uv_req.data = &req->context;
m_pool.emplace_back(std::unique_ptr<PooledUdpSend>(req));
m_locks[i].store(false, std::memory_order_relaxed);
}
}
std::optional<std::tuple<uv_udp_send_t*, char*, EmbeddedContext*>> acquire() {
size_t cap = m_capacity.load(std::memory_order_acquire);
for (size_t i = 0; i < cap; ++i) {
size_t index = m_head.fetch_add(1, std::memory_order_relaxed) % cap;
bool expected = false;
if (m_locks[index].compare_exchange_strong(expected, true)) {
auto* req = m_pool[index].get();
LogNetClientDetail("[SendBufferPool] Acquired [{}]", index);
return std::make_tuple(&req->uv_req, req->buffer.data(), &req->context);
}
}
LogNetClient("[SendBufferPool] Growing from [{}] to [{}]", cap, cap * 2);
grow();
return acquireAfterGrowth();
}
void release(EmbeddedContext* ctx) {
if (!ctx || ctx->pool != this || ctx->pool_index >= m_capacity.load(std::memory_order_acquire)) {
LogNetClient("[SendBufferPool] Invalid context release [{}]", ctx ? ctx->pool_index : -1);
return;
}
m_locks[ctx->pool_index].store(false, std::memory_order_release);
LogNetClientDetail("[SendBufferPool] Released [{}]", ctx->pool_index);
}
private:
struct PooledUdpSend {
uv_udp_send_t uv_req;
std::array<char, UDP_BUFFER_SIZE> buffer;
EmbeddedContext context;
};
std::vector<std::unique_ptr<PooledUdpSend>> m_pool;
std::unique_ptr<std::atomic_bool[]> m_locks;
std::atomic<size_t> m_capacity;
std::atomic<size_t> m_head;
std::mutex m_grow_mutex;
void grow() {
std::lock_guard<std::mutex> lock(m_grow_mutex);
size_t old_cap = m_capacity.load(std::memory_order_acquire);
size_t new_cap = old_cap * 2;
m_pool.reserve(new_cap);
for (size_t i = old_cap; i < new_cap; ++i) {
auto* req = new PooledUdpSend();
req->context.pool_index = i;
req->context.pool = this;
req->uv_req.data = &req->context;
m_pool.emplace_back(std::unique_ptr<PooledUdpSend>(req));
}
auto new_locks = std::make_unique<std::atomic_bool[]>(new_cap);
for (size_t i = 0; i < old_cap; ++i) {
new_locks[i].store(m_locks[i].load(std::memory_order_acquire));
}
for (size_t i = old_cap; i < new_cap; ++i) {
new_locks[i].store(false, std::memory_order_relaxed);
}
m_locks = std::move(new_locks);
m_capacity.store(new_cap, std::memory_order_release);
LogNetClient("[SendBufferPool] Grew to [{}] from [{}]", new_cap, old_cap);
}
std::optional<std::tuple<uv_udp_send_t*, char*, EmbeddedContext*>> acquireAfterGrowth() {
size_t cap = m_capacity.load(std::memory_order_acquire);
for (size_t i = 0; i < cap; ++i) {
size_t index = m_head.fetch_add(1, std::memory_order_relaxed) % cap;
bool expected = false;
if (m_locks[index].compare_exchange_strong(expected, true)) {
auto* req = m_pool[index].get();
LogNetClient("[SendBufferPool] Acquired after grow [{}]", index);
return std::make_tuple(&req->uv_req, req->buffer.data(), &req->context);
}
}
return std::nullopt;
}
};
-1
View File
@@ -171,4 +171,3 @@ namespace EQ
};
}
}
+3 -3
View File
@@ -62,15 +62,15 @@ void EQ::Net::ServertalkClient::Connect()
m_connecting = true;
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
if (connection == nullptr) {
LogNetTCP("Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
LogF(Logs::General, Logs::TCPConnection, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
m_connecting = false;
return;
}
LogNetTCP("Connected to {0}:{1}", m_addr, m_port);
LogF(Logs::General, Logs::TCPConnection, "Connected to {0}:{1}", m_addr, m_port);
m_connection = connection;
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
LogNetTCP("Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
LogF(Logs::General, Logs::TCPConnection, "Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
m_connection.reset();
});
@@ -58,15 +58,15 @@ void EQ::Net::ServertalkLegacyClient::Connect()
m_connecting = true;
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
if (connection == nullptr) {
LogNetTCP("Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
LogF(Logs::General, Logs::TCPConnection, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
m_connecting = false;
return;
}
LogNetTCP("Connected to {0}:{1}", m_addr, m_port);
LogF(Logs::General, Logs::TCPConnection, "Connected to {0}:{1}", m_addr, m_port);
m_connection = connection;
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
LogNetTCP("Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
LogF(Logs::General, Logs::TCPConnection, "Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
m_connection.reset();
});
@@ -131,7 +131,7 @@ void EQ::Net::ServertalkLegacyClient::ProcessReadBuffer()
}
else {
EQ::Net::StaticPacket p(&m_buffer[current + 4], length);
auto cb = m_message_callbacks.find(opcode);
if (cb != m_message_callbacks.end()) {
cb->second(opcode, p);
+55 -108
View File
@@ -1,8 +1,5 @@
#include "tcp_connection.h"
#include "../event/event_loop.h"
#include <iostream>
WriteReqPool tcp_write_pool;
void on_close_handle(uv_handle_t* handle) {
delete (uv_tcp_t *)handle;
@@ -67,37 +64,36 @@ void EQ::Net::TCPConnection::Connect(const std::string &addr, int port, bool ipv
});
}
void EQ::Net::TCPConnection::Start()
{
uv_read_start(
(uv_stream_t *) m_socket, [](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
if (suggested_size > 65536) {
buf->base = new char[suggested_size];
buf->len = suggested_size;
return;
}
void EQ::Net::TCPConnection::Start() {
uv_read_start((uv_stream_t*)m_socket, [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
buf->base = new char[suggested_size];
buf->len = suggested_size;
}, [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
static thread_local char temp_buf[65536];
buf->base = temp_buf;
buf->len = 65536;
}, [](uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
auto *connection = (TCPConnection *) stream->data;
TCPConnection *connection = (TCPConnection*)stream->data;
if (nread > 0) {
connection->Read(buf->base, nread);
}
else if (nread == UV_EOF) {
connection->Disconnect();
}
else if (nread < 0) {
connection->Disconnect();
}
if (nread > 0) {
connection->Read(buf->base, nread);
if (buf->len > 65536) {
delete [] buf->base;
if (buf->base) {
delete[] buf->base;
}
}
);
else if (nread == UV_EOF) {
connection->Disconnect();
if (buf->base) {
delete[] buf->base;
}
}
else if (nread < 0) {
connection->Disconnect();
if (buf->base) {
delete[] buf->base;
}
}
});
}
void EQ::Net::TCPConnection::OnRead(std::function<void(TCPConnection*, const unsigned char*, size_t)> cb)
@@ -134,92 +130,43 @@ void EQ::Net::TCPConnection::Read(const char *data, size_t count)
}
}
void EQ::Net::TCPConnection::Write(const char* data, size_t count) {
if (!m_socket || !data || count == 0) {
std::cerr << "TCPConnection::Write - Invalid socket or data\n";
void EQ::Net::TCPConnection::Write(const char *data, size_t count)
{
if (!m_socket) {
return;
}
if (count <= TCP_BUFFER_SIZE) {
// Fast path: use pooled request with embedded buffer
auto req_opt = tcp_write_pool.acquire();
if (!req_opt) {
std::cerr << "TCPConnection::Write - Out of write requests\n";
return;
struct WriteBaton
{
TCPConnection *connection;
char *buffer;
};
WriteBaton *baton = new WriteBaton;
baton->connection = this;
baton->buffer = new char[count];
uv_write_t *write_req = new uv_write_t;
memset(write_req, 0, sizeof(uv_write_t));
write_req->data = baton;
uv_buf_t send_buffers[1];
memcpy(baton->buffer, data, count);
send_buffers[0] = uv_buf_init(baton->buffer, count);
uv_write(write_req, (uv_stream_t*)m_socket, send_buffers, 1, [](uv_write_t* req, int status) {
WriteBaton *baton = (WriteBaton*)req->data;
delete[] baton->buffer;
delete req;
if (status < 0) {
baton->connection->Disconnect();
}
TCPWriteReq* write_req = *req_opt;
// Fill buffer and set context
memcpy(write_req->buffer.data(), data, count);
write_req->connection = this;
write_req->magic = 0xC0FFEE;
uv_buf_t buf = uv_buf_init(write_req->buffer.data(), static_cast<unsigned int>(count));
int result = uv_write(
&write_req->req,
reinterpret_cast<uv_stream_t*>(m_socket),
&buf,
1,
[](uv_write_t* req, int status) {
auto* full_req = reinterpret_cast<TCPWriteReq*>(req);
if (full_req->magic != 0xC0FFEE) {
std::cerr << "uv_write callback - invalid magic, skipping release\n";
return;
}
tcp_write_pool.release(full_req);
if (status < 0 && full_req->connection) {
std::cerr << "uv_write failed: " << uv_strerror(status) << std::endl;
full_req->connection->Disconnect();
}
}
);
if (result < 0) {
std::cerr << "uv_write() failed immediately: " << uv_strerror(result) << std::endl;
tcp_write_pool.release(write_req);
}
} else {
// Slow path: allocate heap buffer for large write
LogNetTCP("[TCPConnection] Large write of [{}] bytes, using heap buffer", count);
char* heap_buffer = new char[count];
memcpy(heap_buffer, data, count);
uv_write_t* write_req = new uv_write_t;
write_req->data = heap_buffer;
uv_buf_t buf = uv_buf_init(heap_buffer, static_cast<unsigned int>(count));
int result = uv_write(
write_req,
reinterpret_cast<uv_stream_t*>(m_socket),
&buf,
1,
[](uv_write_t* req, int status) {
char* data = static_cast<char*>(req->data);
delete[] data;
delete req;
if (status < 0) {
std::cerr << "uv_write (large) failed: " << uv_strerror(status) << std::endl;
}
}
);
if (result < 0) {
std::cerr << "uv_write() (large) failed immediately: " << uv_strerror(result) << std::endl;
delete[] heap_buffer;
delete write_req;
}
}
delete baton;
});
}
std::string EQ::Net::TCPConnection::LocalIP() const
{
sockaddr_storage addr;
+1 -2
View File
@@ -1,6 +1,5 @@
#pragma once
#include "tcp_connection_pooling.h"
#include <functional>
#include <string>
#include <memory>
@@ -17,7 +16,7 @@ namespace EQ
~TCPConnection();
static void Connect(const std::string &addr, int port, bool ipv6, std::function<void(std::shared_ptr<TCPConnection>)> cb);
void Start();
void OnRead(std::function<void(TCPConnection*, const unsigned char *, size_t)> cb);
void OnDisconnect(std::function<void(TCPConnection*)> cb);
-125
View File
@@ -1,125 +0,0 @@
#pragma once
#include "../eqemu_logsys.h"
#include <vector>
#include <array>
#include <atomic>
#include <memory>
#include <optional>
#include <mutex>
#include <uv.h>
#include <iostream>
namespace EQ { namespace Net { class TCPConnection; } }
constexpr size_t TCP_BUFFER_SIZE = 8192;
struct TCPWriteReq {
uv_write_t req{};
std::array<char, TCP_BUFFER_SIZE> buffer{};
size_t buffer_index{};
EQ::Net::TCPConnection* connection{};
uint32_t magic = 0xC0FFEE;
};
class WriteReqPool {
public:
explicit WriteReqPool(size_t initial_capacity = 512)
: m_capacity(initial_capacity), m_head(0) {
initialize_pool(m_capacity);
}
std::optional<TCPWriteReq*> acquire() {
size_t cap = m_capacity.load(std::memory_order_acquire);
for (size_t i = 0; i < cap; ++i) {
size_t index = m_head.fetch_add(1, std::memory_order_relaxed) % cap;
bool expected = false;
if (m_locks[index].compare_exchange_strong(expected, true, std::memory_order_acquire)) {
LogNetTCPDetail("[WriteReqPool] Acquired buffer index [{}]", index);
return m_reqs[index].get();
}
}
LogNetTCP("[WriteReqPool] Growing from [{}] to [{}]", cap, cap * 2);
grow();
return acquireAfterGrow();
}
void release(TCPWriteReq* req) {
if (!req) return;
const size_t index = req->buffer_index;
const size_t cap = m_capacity.load(std::memory_order_acquire);
if (index >= cap || m_reqs[index].get() != req) {
std::cerr << "WriteReqPool::release - Invalid or stale pointer (index=" << index << ")\n";
return;
}
m_locks[index].store(false, std::memory_order_release);
LogNetTCPDetail("[WriteReqPool] Released buffer index [{}]", index);
}
private:
std::vector<std::unique_ptr<TCPWriteReq>> m_reqs;
std::unique_ptr<std::atomic_bool[]> m_locks;
std::atomic<size_t> m_capacity;
std::atomic<size_t> m_head;
std::mutex m_grow_mutex;
void initialize_pool(size_t count) {
m_reqs.reserve(count);
m_locks = std::make_unique<std::atomic_bool[]>(count);
for (size_t i = 0; i < count; ++i) {
auto req = std::make_unique<TCPWriteReq>();
req->buffer_index = i;
req->req.data = req.get(); // optional: for use in libuv callbacks
m_locks[i].store(false, std::memory_order_relaxed);
m_reqs.emplace_back(std::move(req));
}
m_capacity.store(count, std::memory_order_release);
}
void grow() {
std::lock_guard<std::mutex> lock(m_grow_mutex);
const size_t old_cap = m_capacity.load(std::memory_order_acquire);
const size_t new_cap = old_cap * 2;
m_reqs.reserve(new_cap);
for (size_t i = old_cap; i < new_cap; ++i) {
auto req = std::make_unique<TCPWriteReq>();
req->buffer_index = i;
req->req.data = req.get(); // optional
m_reqs.emplace_back(std::move(req));
}
auto new_locks = std::make_unique<std::atomic_bool[]>(new_cap);
for (size_t i = 0; i < old_cap; ++i) {
new_locks[i].store(m_locks[i].load(std::memory_order_acquire));
}
for (size_t i = old_cap; i < new_cap; ++i) {
new_locks[i].store(false, std::memory_order_relaxed);
}
m_locks = std::move(new_locks);
m_capacity.store(new_cap, std::memory_order_release);
}
std::optional<TCPWriteReq*> acquireAfterGrow() {
const size_t cap = m_capacity.load(std::memory_order_acquire);
for (size_t i = 0; i < cap; ++i) {
bool expected = false;
if (m_locks[i].compare_exchange_strong(expected, true, std::memory_order_acquire)) {
LogNetTCP("[WriteReqPool] Acquired buffer index [{}] after grow", i);
return m_reqs[i].get();
}
}
return std::nullopt;
}
};
@@ -25,7 +25,6 @@ public:
uint8_t is_global;
uint32_t start_time;
uint32_t duration;
uint64_t expire_at;
uint8_t never_expires;
std::string notes;
};
@@ -44,7 +43,6 @@ public:
"is_global",
"start_time",
"duration",
"expire_at",
"never_expires",
"notes",
};
@@ -59,7 +57,6 @@ public:
"is_global",
"start_time",
"duration",
"expire_at",
"never_expires",
"notes",
};
@@ -108,7 +105,6 @@ public:
e.is_global = 0;
e.start_time = 0;
e.duration = 0;
e.expire_at = 0;
e.never_expires = 0;
e.notes = "";
@@ -153,9 +149,8 @@ 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.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] : "";
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
e.notes = row[7] ? row[7] : "";
return e;
}
@@ -194,9 +189,8 @@ 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.expire_at));
v.push_back(columns[7] + " = " + std::to_string(e.never_expires));
v.push_back(columns[8] + " = '" + Strings::Escape(e.notes) + "'");
v.push_back(columns[6] + " = " + std::to_string(e.never_expires));
v.push_back(columns[7] + " = '" + Strings::Escape(e.notes) + "'");
auto results = db.QueryDatabase(
fmt::format(
@@ -224,7 +218,6 @@ 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) + "'");
@@ -262,7 +255,6 @@ 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) + "'");
@@ -304,9 +296,8 @@ 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.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] : "";
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
e.notes = row[7] ? row[7] : "";
all_entries.push_back(e);
}
@@ -337,9 +328,8 @@ 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.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] : "";
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
e.notes = row[7] ? row[7] : "";
all_entries.push_back(e);
}
@@ -420,7 +410,6 @@ 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) + "'");
@@ -451,7 +440,6 @@ 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,11 +19,10 @@
class BaseRespawnTimesRepository {
public:
struct RespawnTimes {
int32_t id;
int32_t start;
int32_t duration;
uint32_t expire_at;
int16_t instance_id;
int32_t id;
int32_t start;
int32_t duration;
int16_t instance_id;
};
static std::string PrimaryKey()
@@ -37,7 +36,6 @@ public:
"id",
"start",
"duration",
"expire_at",
"instance_id",
};
}
@@ -48,7 +46,6 @@ public:
"id",
"start",
"duration",
"expire_at",
"instance_id",
};
}
@@ -93,7 +90,6 @@ public:
e.id = 0;
e.start = 0;
e.duration = 0;
e.expire_at = 0;
e.instance_id = 0;
return e;
@@ -134,8 +130,7 @@ 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.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;
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
return e;
}
@@ -172,8 +167,7 @@ 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.expire_at));
v.push_back(columns[4] + " = " + std::to_string(e.instance_id));
v.push_back(columns[3] + " = " + std::to_string(e.instance_id));
auto results = db.QueryDatabase(
fmt::format(
@@ -198,7 +192,6 @@ 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(
@@ -232,7 +225,6 @@ 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) + ")");
@@ -270,8 +262,7 @@ 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.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;
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
all_entries.push_back(e);
}
@@ -299,8 +290,7 @@ 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.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;
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
all_entries.push_back(e);
}
@@ -378,7 +368,6 @@ 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(
@@ -405,7 +394,6 @@ 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) + ")");
+40 -2
View File
@@ -7,11 +7,49 @@
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` = {}, `expire_at` = (`duration` + `start_time`) WHERE `{}` = {}",
"UPDATE `{}` SET `duration` = {} WHERE `{}` = {}",
TableName(),
new_duration,
PrimaryKey(),
@@ -27,7 +65,7 @@ public:
auto results = db.QueryDatabase(
fmt::format(
SQL(
SELECT (`expire_at` - UNIX_TIMESTAMP()) AS `remaining` FROM `{}`
SELECT ((start_time + duration) - UNIX_TIMESTAMP()) AS `remaining` FROM `{}`
WHERE `id` = {}
),
TableName(),
+37 -6
View File
@@ -8,11 +8,47 @@
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 `expire_at` < UNIX_TIMESTAMP(NOW())",
"DELETE FROM `{}` WHERE (`start` + `duration`) < UNIX_TIMESTAMP(NOW())",
TableName()
)
);
@@ -41,11 +77,6 @@ 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
+10 -24
View File
@@ -54,31 +54,17 @@ public:
{
BulkTraders_Struct all_entries{};
std::vector<DistinctTraders_Struct> distinct_traders;
MySQLRequestResult results;
if (RuleB(Bazaar, UseAlternateBazaarSearch)) {
results = db.QueryDatabase(fmt::format(
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"FROM trader AS t "
"JOIN character_data AS c ON t.char_id = c.id "
"WHERE t.char_zone_instance_id = {} "
"ORDER BY t.char_zone_instance_id ASC "
"LIMIT {}",
char_zone_instance_id,
max_results)
);
}
else {
results = db.QueryDatabase(fmt::format(
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"FROM trader AS t "
"JOIN character_data AS c ON t.char_id = c.id "
"ORDER BY t.char_zone_instance_id ASC "
"LIMIT {}",
char_zone_instance_id,
max_results)
);
}
auto results = db.QueryDatabase(fmt::format(
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"FROM trader AS t "
"JOIN character_data AS c ON t.char_id = c.id "
"WHERE t.char_zone_instance_id = {} "
"ORDER BY t.char_zone_instance_id ASC "
"LIMIT {}",
char_zone_instance_id,
max_results)
);
distinct_traders.reserve(results.RowCount());
+15 -14
View File
@@ -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, CorpseOwnerOnlineCheckTime, 300, "How often corpse will check if its owner is online DEFAULT: 300 (5 minutes)")
RULE_INT(Character, CorpseOwnerOnlineTime, 30000, "How often corpse will check if its owner is online DEFAULT: 30000 (30 Seconds)")
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")
@@ -261,7 +261,6 @@ 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)
@@ -291,7 +290,6 @@ 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)
@@ -347,7 +345,6 @@ RULE_STRING(World, SupportedClients, "RoF2", "Comma-delimited list of clients to
RULE_STRING(World, CustomFilesKey, "", "Enable if the server requires custom files and sends a key to validate. Empty string to disable. Example: eqcustom_v1")
RULE_STRING(World, CustomFilesUrl, "github.com/knervous/eqnexus/releases", "URL to display at character select if client is missing custom files")
RULE_INT(World, CustomFilesAdminLevel, 20, "Admin level at which custom file key is not required when CustomFilesKey is specified")
RULE_BOOL(World, RealTimeCalculateGuilds, false, "(Temp feature flag) If true, guilds will be calculated in real time instead of at zone boot. This is a performance hit but allows for more dynamic guilds.")
RULE_CATEGORY_END()
RULE_CATEGORY(Zone)
@@ -382,7 +379,6 @@ 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)
@@ -392,10 +388,9 @@ 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, 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_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_CATEGORY_END()
RULE_CATEGORY(Pathing)
@@ -819,7 +814,6 @@ 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%.")
@@ -831,6 +825,8 @@ 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.")
@@ -847,7 +843,7 @@ RULE_BOOL(Bots, AllowCastAAs, true, "If enabled, players can use ^cast aa to cas
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 usable spell list to cast. Empty uses Manifest Elements - 'SumMageMultiElement'")
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_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")
@@ -858,7 +854,15 @@ RULE_BOOL(Bots, BotArcheryConsumesAmmo, true, "Set to false to disable Archery A
RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption")
RULE_INT(Bots, StackSizeMin, 20, "20 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).")
RULE_INT(Bots, HasOrMayGetAggroThreshold, 90, "90 Default. Percent threshold of total hate where bots will stop casting spells that generate hate if they are set to try to not pull aggro via spells.")
RULE_REAL(Bots, LowerMeleeDistanceMultiplier, 0.35, "Closest % of the hit box a melee bot will get to the target. Default 0.35")
RULE_REAL(Bots, LowerTauntingMeleeDistanceMultiplier, 0.25, "Closest % of the hit box a taunting melee bot will get to the target. Default 0.25")
RULE_REAL(Bots, LowerMaxMeleeRangeDistanceMultiplier, 0.80, "Closest % of the hit box a max melee range melee bot will get to the target. Default 0.80")
RULE_REAL(Bots, UpperMeleeDistanceMultiplier, 0.55, "Furthest % of the hit box a melee bot will get from the target. Default 0.55")
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.")
@@ -899,7 +903,6 @@ 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)
@@ -923,7 +926,6 @@ RULE_BOOL(Chat, AutoInjectSaylinksToSay, true, "Automatically injects saylinks i
RULE_BOOL(Chat, AutoInjectSaylinksToClientMessage, true, "Automatically injects saylinks into dialogue that has [brackets in them]")
RULE_BOOL(Chat, QuestDialogueUsesDialogueWindow, false, "Pipes all quest dialogue to dialogue window")
RULE_BOOL(Chat, DialogueWindowAnimatesNPCsIfNoneSet, true, "If there is no animation specified in the dialogue window markdown then it will choose a random greet animation such as wave or salute")
RULE_BOOL(Chat, AlwaysCaptureCommandText, false, "Consume command text (# and ^ by default), regardless of which channel it is sent to")
RULE_CATEGORY_END()
RULE_CATEGORY(Merchant)
@@ -1092,7 +1094,6 @@ 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)
-2
View File
@@ -22,7 +22,6 @@ namespace ServerReload {
LevelEXPMods,
Logs,
Loot,
Maps,
Merchants,
NPCEmotes,
NPCSpells,
@@ -62,7 +61,6 @@ namespace ServerReload {
"Level EXP Mods",
"Logs",
"Loot",
"Maps",
"Merchants",
"NPC Emotes",
"NPC Spells",
+12
View File
@@ -2808,6 +2808,18 @@ 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;
+3 -4
View File
@@ -900,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);
@@ -917,8 +917,6 @@ 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
@@ -1816,6 +1814,7 @@ 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);
-28
View File
@@ -468,31 +468,3 @@ 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;
}
-22
View File
@@ -196,25 +196,3 @@ 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;
}
-1
View File
@@ -51,7 +51,6 @@ 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
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "23.5.0-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "23.3.4-dev" // always append -dev to the current version for custom-builds
#define LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__
@@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9321
#define CURRENT_BINARY_DATABASE_VERSION 9314
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
#endif
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "23.5.0",
"version": "23.3.4",
"repository": {
"type": "git",
"url": "https://github.com/EQEmu/Server.git"
+1 -1
View File
@@ -177,7 +177,7 @@ int main()
}
if (player_event_process_timer.Check()) {
player_event_logs.Process();
std::jthread player_event_thread(&PlayerEventLogs::Process, &player_event_logs);
}
};
+2 -2
View File
@@ -177,7 +177,7 @@ void ChatChannelList::SendAllChannels(Client *c) {
std::string Message;
char CountString[13];
char CountString[10];
while(iterator.MoreElements()) {
@@ -408,7 +408,7 @@ void ChatChannel::SendChannelMembers(Client *c) {
if(!c) return;
char CountString[13];
char CountString[10];
sprintf(CountString, "(%i)", MemberCount(c->GetAccountStatus()));
-1
View File
@@ -57,7 +57,6 @@ 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."
+2
View File
@@ -33,6 +33,7 @@ bash -c "${world_bin} database:dump --login-tables --drop-table-syntax-only --du
bash -c "${world_bin} database:dump --player-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_player.sql"
bash -c "${world_bin} database:dump --system-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_system.sql"
bash -c "${world_bin} database:dump --state-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_state.sql"
bash -c "${world_bin} database:dump --query-serv-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_queryserv.sql"
#############################################
# generate "create_" table files
@@ -44,6 +45,7 @@ bash -c "${world_bin} database:dump --login-tables --table-structure-only --dump
bash -c "${world_bin} database:dump --player-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_player.sql"
bash -c "${world_bin} database:dump --state-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_state.sql"
bash -c "${world_bin} database:dump --static-instance-data --dump-output-to-console >> ${dump_path}create_tables_state.sql"
bash -c "${world_bin} database:dump --query-serv-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_queryserv.sql"
# with content
bash -c "${world_bin} database:dump --content-tables --dump-output-to-console > ${dump_path}create_tables_content.sql"
+51 -163
View File
@@ -42,16 +42,11 @@ extern ZSList zoneserver_list;
uint32 numplayers = 0; //this really wants to be a member variable of ClientList...
ClientList::ClientList()
: CLStale_timer(10000),
m_poll_cache_timer(6000)
: CLStale_timer(10000)
{
NextCLEID = 1;
m_tick = std::make_unique<EQ::Timer>(5000, true, std::bind(&ClientList::OnTick, this, std::placeholders::_1));
// pre-allocate / pin memory for the zone server caches
m_gm_zone_server_ids.reserve(512);
m_guild_zone_server_ids.reserve(1024);
}
ClientList::~ClientList() {
@@ -62,10 +57,6 @@ void ClientList::Process() {
if (CLStale_timer.Check())
CLCheckStale();
if (m_poll_cache_timer.Check()) {
RebuildZoneServerCaches();
}
LinkedListIterator<Client*> iterator(list);
iterator.Reset();
@@ -393,7 +384,6 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
}
else {
cle->Update(zoneserver, scl);
AddToZoneServerCaches(cle);
}
return;
}
@@ -468,7 +458,6 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
);
clientlist.Insert(cle);
AddToZoneServerCaches(cle);
zoneserver->ChangeWID(scl->charid, cle->GetID());
}
@@ -1619,7 +1608,7 @@ void ClientList::OnTick(EQ::Timer *t)
/**
* @param response
*/
void ClientList::GetClientList(Json::Value &response, bool full_list)
void ClientList::GetClientList(Json::Value &response)
{
LinkedListIterator<ClientListEntry *> Iterator(clientlist);
@@ -1630,68 +1619,62 @@ void ClientList::GetClientList(Json::Value &response, bool full_list)
Json::Value row;
row["id"] = cle->GetID();
row["name"] = cle->name();
row["level"] = cle->level();
row["ip"] = cle->GetIP();
row["gm"] = cle->GetGM();
row["race"] = cle->race();
row["class"] = cle->class_();
row["client_version"] = cle->GetClientVersion();
row["admin"] = cle->Admin();
row["account_id"] = cle->AccountID();
row["account_name"] = cle->AccountName();
row["character_id"] = cle->CharID();
row["anon"] = cle->Anon();
row["guild_id"] = cle->GuildID();
if (full_list) {
row["loginserver_account_id"] = cle->LSAccountID();
row["loginserver_id"] = cle->LSID();
row["loginserver_name"] = cle->LSName();
row["online"] = cle->Online();
row["world_admin"] = cle->WorldAdmin();
row["guild_rank"] = cle->GuildRank();
row["guild_tribute_opt_in"] = cle->GuildTributeOptIn();
row["instance"] = cle->instance();
row["is_local_client"] = cle->IsLocalClient();
row["lfg"] = cle->LFG();
row["lfg_comments"] = cle->GetLFGComments();
row["lfg_from_level"] = cle->GetLFGFromLevel();
row["lfg_match_filter"] = cle->GetLFGMatchFilter();
row["lfg_to_level"] = cle->GetLFGToLevel();
row["tells_off"] = cle->TellsOff();
row["zone"] = cle->zone();
}
row["account_id"] = cle->AccountID();
row["account_name"] = cle->AccountName();
row["admin"] = cle->Admin();
row["id"] = cle->GetID();
row["ip"] = cle->GetIP();
row["loginserver_account_id"] = cle->LSAccountID();
row["loginserver_id"] = cle->LSID();
row["loginserver_name"] = cle->LSName();
row["online"] = cle->Online();
row["world_admin"] = cle->WorldAdmin();
auto server = cle->Server();
if (server) {
row["server"]["zone_id"] = server->GetZoneID();
row["server"]["zone_long_name"] = server->GetZoneLongName();
row["server"]["zone_name"] = server->GetZoneName();
row["server"]["zone_os_pid"] = server->GetZoneOSProcessID();
row["server"]["id"] = server->GetID();
if (full_list) {
row["server"]["client_address"] = server->GetCAddress();
row["server"]["client_local_address"] = server->GetCLocalAddress();
row["server"]["client_port"] = server->GetCPort();
row["server"]["compile_time"] = server->GetCompileTime();
row["server"]["instance_id"] = server->GetInstanceID();
row["server"]["ip"] = server->GetIP();
row["server"]["is_booting"] = server->IsBootingUp();
row["server"]["launch_name"] = server->GetLaunchName();
row["server"]["launched_name"] = server->GetLaunchedName();
row["server"]["number_players"] = server->NumPlayers();
row["server"]["port"] = server->GetPort();
row["server"]["previous_zone_id"] = server->GetPrevZoneID();
row["server"]["static_zone"] = server->IsStaticZone();
row["server"]["uui"] = server->GetUUID();
}
row["server"]["client_address"] = server->GetCAddress();
row["server"]["client_local_address"] = server->GetCLocalAddress();
row["server"]["client_port"] = server->GetCPort();
row["server"]["compile_time"] = server->GetCompileTime();
row["server"]["id"] = server->GetID();
row["server"]["instance_id"] = server->GetInstanceID();
row["server"]["ip"] = server->GetIP();
row["server"]["is_booting"] = server->IsBootingUp();
row["server"]["launch_name"] = server->GetLaunchName();
row["server"]["launched_name"] = server->GetLaunchedName();
row["server"]["number_players"] = server->NumPlayers();
row["server"]["port"] = server->GetPort();
row["server"]["previous_zone_id"] = server->GetPrevZoneID();
row["server"]["static_zone"] = server->IsStaticZone();
row["server"]["uui"] = server->GetUUID();
row["server"]["zone_id"] = server->GetZoneID();
row["server"]["zone_long_name"] = server->GetZoneLongName();
row["server"]["zone_name"] = server->GetZoneName();
row["server"]["zone_os_pid"] = server->GetZoneOSProcessID();
}
else {
row["server"] = Json::Value();
}
row["anon"] = cle->Anon();
row["character_id"] = cle->CharID();
row["class"] = cle->class_();
row["client_version"] = cle->GetClientVersion();
row["gm"] = cle->GetGM();
row["guild_id"] = cle->GuildID();
row["guild_rank"] = cle->GuildRank();
row["guild_tribute_opt_in"] = cle->GuildTributeOptIn();
row["instance"] = cle->instance();
row["is_local_client"] = cle->IsLocalClient();
row["level"] = cle->level();
row["lfg"] = cle->LFG();
row["lfg_comments"] = cle->GetLFGComments();
row["lfg_from_level"] = cle->GetLFGFromLevel();
row["lfg_match_filter"] = cle->GetLFGMatchFilter();
row["lfg_to_level"] = cle->GetLFGToLevel();
row["name"] = cle->name();
row["race"] = cle->race();
row["tells_off"] = cle->TellsOff();
row["zone"] = cle->zone();
response.append(row);
@@ -1867,98 +1850,3 @@ std::map<uint32, ClientListEntry *> ClientList::GetGuildClientsWithTributeOptIn(
}
return guild_members;
}
void ClientList::RebuildZoneServerCaches()
{
// Clear without freeing memory (buckets stay allocated)
m_gm_zone_server_ids.clear();
m_guild_zone_server_ids.clear();
LinkedListIterator<ClientListEntry*> iterator(clientlist);
iterator.Reset();
while (iterator.MoreElements()) {
ClientListEntry* cle = iterator.GetData();
if (cle->Online() != CLE_Status::InZone || !cle->Server()) {
iterator.Advance();
continue;
}
uint32_t server_id = cle->Server()->GetID();
// Track GM zone server
if (cle->GetGM()) {
m_gm_zone_server_ids.insert(server_id);
}
// Track guild zone servers
if (cle->GuildID() > 0) {
auto& guild_set = m_guild_zone_server_ids[cle->GuildID()];
guild_set.insert(server_id);
}
iterator.Advance();
}
}
std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
{
if (RuleB(World, RealTimeCalculateGuilds)) {
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;
}
auto it = m_guild_zone_server_ids.find(guild_id);
if (it == m_guild_zone_server_ids.end()) {
return {};
}
return {it->second.begin(), it->second.end()};
}
void ClientList::AddToZoneServerCaches(ClientListEntry* cle)
{
if (!cle || cle->Online() != CLE_Status::InZone || !cle->Server()) {
return;
}
uint32_t server_id = cle->Server()->GetID();
// Add GM zone server if applicable
if (cle->GetGM()) {
m_gm_zone_server_ids.insert(server_id);
}
// Add guild zone server if applicable
if (cle->GuildID() > 0) {
m_guild_zone_server_ids[cle->GuildID()].insert(server_id);
}
}
+1 -15
View File
@@ -66,7 +66,7 @@ public:
int GetClientCount();
void GetClients(const char *zone_name, std::vector<ClientListEntry *> &into);
void GetClientList(Json::Value &response, bool full_list = false);
void GetClientList(Json::Value &response);
void GetGuildClientList(Json::Value& response, uint32 guild_id);
void SendCharacterMessage(uint32_t character_id, int chat_type, const std::string& message);
@@ -76,15 +76,6 @@ public:
void SendCharacterMessageID(const std::string& character_name, int chat_type, int eqstr_id, std::initializer_list<std::string> args = {});
void SendCharacterMessageID(ClientListEntry* character, int chat_type, int eqstr_id, std::initializer_list<std::string> args = {});
void AddToZoneServerCaches(ClientListEntry* cle);
void RebuildZoneServerCaches();
std::vector<uint32_t> GetGuildZoneServers(uint32 guild_id);
inline std::vector<uint32_t> GetZoneServersWithGMs()
{
return {m_gm_zone_server_ids.begin(), m_gm_zone_server_ids.end()};
}
private:
void OnTick(EQ::Timer *t);
inline uint32 GetNextCLEID() { return NextCLEID++; }
@@ -99,11 +90,6 @@ private:
std::unique_ptr<EQ::Timer> m_tick;
// Zone server routing caches
Timer m_poll_cache_timer;
std::unordered_set<uint32_t> m_gm_zone_server_ids;
std::unordered_map<uint32_t, std::unordered_set<uint32_t>> m_guild_zone_server_ids;
};
#endif /*CLIENTLIST_H_*/
+2 -5
View File
@@ -137,11 +137,8 @@ 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
}
+4 -22
View File
@@ -32,8 +32,6 @@ 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();
@@ -111,17 +109,9 @@ void callGetDatabaseSchema(Json::Value &response)
response.append(schema);
}
void callGetClientList(Json::Value &response, const std::vector<std::string> &args)
void callGetClientList(Json::Value &response)
{
// if args has "full"
bool full_list = false;
if (args.size() > 1) {
if (args[1] == "full") {
full_list = true;
}
}
client_list.GetClientList(response, full_list);
client_list.GetClientList(response);
}
void getReloadTypes(Json::Value &response)
@@ -135,12 +125,6 @@ void getReloadTypes(Json::Value &response)
}
}
void getServerCounts(Json::Value &response, const std::vector<std::string> &args)
{
response["zone_count"] = zoneserver_list.GetServerListCount();
response["client_count"] = client_list.GetClientCount();
}
void EQEmuApiWorldDataService::reload(Json::Value &r, const std::vector<std::string> &args)
{
std::vector<std::string> commands{};
@@ -188,7 +172,7 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
callGetDatabaseSchema(r);
}
if (m == "get_client_list") {
callGetClientList(r, args);
callGetClientList(r);
}
if (m == "get_reload_types") {
getReloadTypes(r);
@@ -199,9 +183,6 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
if (m == "get_guild_details") {
callGetGuildDetails(r, args);
}
if (m == "get_server_counts") {
getServerCounts(r, args);
}
if (m == "lock_status") {
r["locked"] = WorldConfig::get()->Locked;
}
@@ -209,6 +190,7 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
void EQEmuApiWorldDataService::callGetGuildDetails(Json::Value &response, const std::vector<std::string> &args)
{
std::string command = !args[1].empty() ? args[1] : "";
if (command.empty()) {
return;
+1 -1
View File
@@ -286,7 +286,7 @@ void LoginServer::ProcessLSFatalError(uint16_t opcode, EQ::Net::Packet &p)
if (error.find("Worldserver Account / Password INVALID") != std::string::npos) {
reason = "Usually this indicates you do not have a valid [account] and [password] (worldserver) account associated with your loginserver configuration. ";
if (fmt::format("{}", m_loginserver_address).find("login.eqemulator.net") != std::string::npos) {
reason += "For Legacy EQEmulator connections, you need to register your server @ https://www.eqemulator.org/index.php?pageid=ws_mgmt";
reason += "For Legacy EQEmulator connections, you need to register your server @ http://www.eqemulator.org/account/?LS";
}
}
+5 -11
View File
@@ -381,19 +381,11 @@ 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();
@@ -456,6 +448,10 @@ 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();
@@ -506,8 +502,6 @@ 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();
+17 -22
View File
@@ -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.SendPacketToZonesWithGuild(guild_id, pack);
zoneserver_list.SendPacket(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.SendPacketToZonesWithGuild(guild_id, pack);
zoneserver_list.SendPacket(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.SendPacketToZonesWithGuild(guild_id, pack);
zoneserver_list.SendPacket(pack);
safe_delete(pack);
}
@@ -85,14 +85,15 @@ 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)) {
BaseGuildManager::RefreshGuild(s->guild_id);
LogGuilds("Unable to preform local refresh on guild [{}]", s->guild_id);
//can we do anything?
}
//broadcast this packet to all zones.
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
break;
}
@@ -107,7 +108,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.SendPacketToZonesWithGuild(s->guild_id, pack);
zoneserver_list.SendPacket(pack);
break;
}
@@ -146,7 +147,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
auto s = (ServerGuildID_Struct *)pack->pBuffer;
RefreshGuild(s->guild_id);
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
zoneserver_list.SendPacket(pack);
break;
}
case ServerOP_GuildPermissionUpdate:
@@ -178,7 +179,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
sg->function_value
);
zoneserver_list.SendPacketToZonesWithGuild(sg->guild_id, pack);
zoneserver_list.SendPacketToBootedZones(pack);
}
else {
LogError("World Received ServerOP_GuildPermissionUpdate for guild [{}] function id {} with value of {} but guild could not be found.",
@@ -212,7 +213,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
rnc->rank,
rnc->rank_name
);
zoneserver_list.SendPacketToZonesWithGuild(rnc->guild_id, pack);
zoneserver_list.SendPacketToBootedZones(pack);
}
else {
LogError("World Received ServerOP_GuildRankNameChange from zone for guild [{}] rank id {} with new name of {} but could not find guild.",
@@ -229,13 +230,13 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
case ServerOP_GuildChannel:
case ServerOP_GuildURL:
case ServerOP_GuildMemberRemove:
case ServerOP_GuildSendGuildList:
case ServerOP_GuildMembersList:
{
auto in = (ServerOP_GuildMessage_Struct *) pack->pBuffer;
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, pack);
zoneserver_list.SendPacketToBootedZones(pack);
break;
}
case ServerOP_GuildMemberAdd:
case ServerOP_GuildMemberAdd:
{
auto in = (ServerOP_GuildMessage_Struct *)pack->pBuffer;
auto guild = GetGuildByGuildID(in->guild_id);
@@ -243,15 +244,9 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
BaseGuildManager::RefreshGuild(in->guild_id);
}
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, pack);
zoneserver_list.SendPacketToBootedZones(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;
@@ -456,6 +451,6 @@ void WorldGuildManager::SendGuildTributeFavorAndTimer(uint32 guild_id, uint32 fa
data->tribute_timer = time;
data->trophy_timer = 0;
zoneserver_list.SendPacketToZonesWithGuild(guild_id, sp);
zoneserver_list.SendPacketToBootedZones(sp);
safe_delete(sp)
}
+279 -243
View File
@@ -29,10 +29,6 @@
#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;
@@ -54,177 +50,187 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o
character_limit = 8;
}
auto characters = CharacterDataRepository::GetWhere(
database,
fmt::format(
"`account_id` = {} AND `deleted_at` IS NULL ORDER BY `name` LIMIT {}",
account_id,
character_limit
)
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
);
size_t character_count = characters.size();
if (characters.empty()) {
auto results = database.QueryDatabase(character_list_query);
size_t character_count = results.RowCount();
if (character_count == 0) {
*out_app = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct));
auto *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer;
cs->CharCount = 0;
CharacterSelect_Struct *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;
auto *cs = (CharacterSelect_Struct *) buff_ptr;
CharacterSelect_Struct *cs = (CharacterSelect_Struct *) buff_ptr;
cs->CharCount = character_count;
cs->CharCount = character_count;
cs->TotalChars = character_limit;
buff_ptr += sizeof(CharacterSelect_Struct);
for (auto &e: characters) {
auto *cse = (CharacterSelectEntry_Struct *) buff_ptr;
PlayerProfile_Struct pp;
EQ::InventoryProfile inv;
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;
pp.SetPlayerProfileVersion(EQ::versions::ConvertClientVersionToMobVersion(client_version));
inv.SetInventoryVersion(client_version);
inv.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support
inventory_profile.SetInventoryVersion(client_version);
inventory_profile.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support
uint32 character_id = e.id;
uint8 has_home = 0;
uint8 has_bind = 0;
uint32 character_id = Strings::ToUnsignedInt(row[0]);
uint8 has_home = 0;
uint8 has_bind = 0;
memset(&pp, 0, sizeof(PlayerProfile_Struct));
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;
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]);
for (auto &s: cse->Equip) {
s.Material = 0;
s.Unknown1 = 0;
s.EliteModel = 0;
s.HerosForgeModel = 0;
s.Unknown2 = 0;
s.Color = 0;
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;
}
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;
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;
if (RuleB(World, EnableReturnHomeButton)) {
int now = time(nullptr);
if (now - e.last_login >= RuleI(World, MinOfflineTimeToReturnHome)) {
cse->GoHome = 1;
}
if ((now - Strings::ToInt(row[7])) >= RuleI(World, MinOfflineTimeToReturnHome))
p_character_select_entry_struct->GoHome = 1;
}
if (RuleB(World, EnableTutorialButton) && (cse->Level <= RuleI(World, MaxLevelForTutorial))) {
cse->Tutorial = 1;
}
if (RuleB(World, EnableTutorialButton) && (p_character_select_entry_struct->Level <= RuleI(World, MaxLevelForTutorial)))
p_character_select_entry_struct->Tutorial = 1;
// binds
int bind_count = 0;
for (auto &bind : character_binds) {
if (bind.id != e.id) {
continue;
}
if (bind.slot == 4) {
/**
* 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;
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 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]);
}
}
if (bind.slot == 0) {
if (row_b[6] && Strings::ToInt(row_b[6]) == 0)
has_bind = 1;
}
bind_count++;
}
if (has_home == 0 || has_bind == 0) {
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()
)
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()
);
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;
@@ -232,151 +238,168 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o
pp.binds[4].z = z->safe_z;
pp.binds[4].heading = z->safe_heading;
}
} 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) {
}
/* 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) {
auto zone = GetZone(pp.binds[4].zone_id);
if (zone) {
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;
x = zone->safe_x;
y = zone->safe_y;
z = zone->safe_z;
heading = zone->safe_heading;
}
}
pp.binds[4].x = x;
pp.binds[4].y = y;
pp.binds[4].z = z;
pp.binds[4].heading = heading;
}
}
else {
LogError("No start zone found for class [{}] deity [{}] race [{}]", cse->Class, cse->Deity, cse->Race);
}
pp.binds[0] = pp.binds[4];
// If we don't have home set, set it
/* If no home bind 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);
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);
}
// If we don't have regular bind set, set it
/* If no regular bind set, set it */
if (has_bind == 0) {
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);
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);
}
}
// 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;
}
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);
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);
}
CharacterBindRepository::ReplaceMany(*this, binds);
}
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;
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]);
}
if (GetCharSelInventory(inventories, e, &inv)) {
const EQ::ItemData *item = nullptr;
const EQ::ItemInstance *inst = nullptr;
int16 inventory_slot = 0;
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;
for (uint32 matslot = EQ::textures::textureBegin; matslot < EQ::textures::materialCount; matslot++) {
inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(matslot);
if (inventory_slot == INVALID_INDEX) { continue; }
inst = inv.GetItem(inventory_slot);
if (inst == nullptr) {
inst = inventory_profile.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();
cse->Equip[matslot].Material = item_id_file;
}
else {
p_character_select_entry_struct->Equip[matslot].Material = item_id_file;
} else {
if (strlen(item->IDFile) > 2) {
item_id_file = Strings::ToInt(&item->IDFile[2]);
cse->Equip[matslot].Material = item_id_file;
p_character_select_entry_struct->Equip[matslot].Material = item_id_file;
}
}
if (matslot == EQ::textures::weaponPrimary) {
cse->PrimaryIDFile = item_id_file;
p_character_select_entry_struct->PrimaryIDFile = item_id_file;
} else {
p_character_select_entry_struct->SecondaryIDFile = item_id_file;
}
else {
cse->SecondaryIDFile = item_id_file;
}
}
else {
} 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()
);
cse->Equip[matslot].Material = item->Material;
cse->Equip[matslot].EliteModel = item->EliteMaterial;
cse->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot);
cse->Equip[matslot].Color = color;
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;
}
}
}
else {
printf("Error loading inventory for %s\n", cse->Name);
} else {
printf("Error loading inventory for %s\n", p_character_select_entry_struct->Name);
}
buff_ptr += sizeof(CharacterSelectEntry_Struct);
}
@@ -826,21 +849,34 @@ bool WorldDatabase::LoadCharacterCreateCombos()
return true;
}
bool WorldDatabase::GetCharSelInventory(
const std::vector<InventoryRepository::Inventory> &inventories,
const CharacterDataRepository::CharacterData &character,
EQ::InventoryProfile *inv
)
// 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)
{
if (inventories.empty() || !character.id || !inv) {
if (!account_id || !name || !inv) {
return false;
}
for (const auto& e : inventories) {
if (e.character_id != character.id) {
continue;
}
const uint32 character_id = GetCharacterID(name);
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:
+1 -7
View File
@@ -20,8 +20,6 @@
#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;
@@ -45,11 +43,7 @@ private:
void SetTitaniumDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc);
void SetSoFDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc);
bool GetCharSelInventory(
const std::vector<InventoryRepository::Inventory> &inventories,
const CharacterDataRepository::CharacterData &character,
EQ::InventoryProfile *inv
);
bool GetCharSelInventory(uint32 account_id, char* name, EQ::InventoryProfile* inv);
};
extern WorldDatabase database;
+14 -69
View File
@@ -36,13 +36,11 @@ 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);
@@ -517,27 +515,19 @@ 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;
@@ -545,37 +535,22 @@ void ZSList::SendEmoteMessageRaw(
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) {
if (to_guilddbid > 0) {
SendPacketToZonesWithGuild(to_guilddbid, pack);
}
else if (to_minstatus > 0) {
SendPacketToZonesWithGMs(pack);
} else {
SendPacket(pack);
}
SendPacket(pack);
}
else {
ZoneServer *zs = FindByName(to);
if (zs) {
ZoneServer* zs = FindByName(to);
if (zs != 0)
zs->SendPacket(pack);
}
else if (to_guilddbid > 0) {
SendPacketToZonesWithGuild(to_guilddbid, pack);
}
else if (to_minstatus > 0) {
SendPacketToZonesWithGMs(pack);
}
else {
else
SendPacket(pack);
}
}
delete pack;
}
@@ -896,34 +871,6 @@ 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)
{
auto servers = client_list.GetZoneServersWithGMs();
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;
}
void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
{
static auto pack = ServerPacket(ServerOP_ServerReloadRequest, sizeof(ServerReload::Request));
@@ -947,8 +894,6 @@ 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
};
-3
View File
@@ -29,8 +29,6 @@ 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);
@@ -72,7 +70,6 @@ public:
ZoneServer* FindByZoneID(uint32 ZoneID);
const std::list<std::unique_ptr<ZoneServer>> &getZoneServerList() const;
inline uint32_t GetServerListCount() { return zone_server_list.size(); }
void SendServerReload(ServerReload::Type type, uchar *packet = nullptr);
private:
+8 -16
View File
@@ -571,13 +571,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
);
}
}
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);
}
zoneserver_list.SendPacket(pack);
}
break;
@@ -735,15 +729,13 @@ 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: Zone ID, Instance ID, nor Zone Short Name specified"
"Error: SOP_ZoneShutdown: neither ID nor name specified"
);
}
@@ -1518,7 +1510,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
guild->tribute.timer.Disable();
zoneserver_list.SendPacketToZonesWithGuild(data->guild_id, pack);
zoneserver_list.SendPacketToBootedZones(pack);
}
break;
}
@@ -1561,7 +1553,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.SendPacketToZonesWithGuild(data->guild_id, pack);
zoneserver_list.SendPacketToBootedZones(pack);
}
break;
}
@@ -1595,7 +1587,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.SendPacketToZonesWithGuild(in->guild_id, out);
zoneserver_list.SendPacketToBootedZones(out);
safe_delete(out);
}
break;
@@ -1618,7 +1610,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.SendPacketToZonesWithGuild(in->guild_id, sp);
zoneserver_list.SendPacketToBootedZones(sp);
safe_delete(sp);
}
@@ -1638,7 +1630,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.SendPacketToZonesWithGuild(in->guild_id, sp);
zoneserver_list.SendPacketToBootedZones(sp);
safe_delete(sp);
}
@@ -1662,7 +1654,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.SendPacketToZonesWithGuild(out->guild_id, sp);
zoneserver_list.SendPacketToBootedZones(sp);
safe_delete(sp)
}
break;
-2
View File
@@ -53,8 +53,6 @@ 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; }
-2
View File
@@ -173,7 +173,6 @@ SET(zone_sources
zone_event_scheduler.cpp
zone_npc_factions.cpp
zone_reload.cpp
zone_save_state.cpp
zoning.cpp
)
@@ -293,7 +292,6 @@ 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})
+1 -7
View File
@@ -2507,12 +2507,6 @@ 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) {
@@ -2633,7 +2627,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)) ||
+12 -9
View File
@@ -1165,6 +1165,10 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
}
case SE_ResistFearChance: {
if (base_value == 100) // If we reach 100% in a single spell/item then we should be immune to
// negative fear resist effects until our immunity is over
newbon->Fearless = true;
newbon->ResistFearChance += base_value; // these should stack
break;
}
@@ -2470,6 +2474,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
case SE_ResistFearChance:
{
if(effect_value == 100) // If we reach 100% in a single spell/item then we should be immune to negative fear resist effects until our immunity is over
new_bonus->Fearless = true;
new_bonus->ResistFearChance += effect_value; // these should stack
break;
}
@@ -4682,7 +4689,11 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
break;
case SE_ResistFearChance:
if (negate_spellbonus) {spellbonuses.ResistFearChance = effect_value; }
if (negate_spellbonus) {
spellbonuses.Fearless = false;
spellbonuses.ResistFearChance = effect_value;
}
if (negate_aabonus) { aabonuses.ResistFearChance = effect_value; }
if (negate_itembonus) { itembonuses.ResistFearChance = effect_value; }
break;
@@ -5320,14 +5331,6 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
spellbonuses.SEResist[e] = effect_value;
spellbonuses.SEResist[e + 1] = effect_value;
}
if (negate_itembonus) {
itembonuses.SEResist[e] = effect_value;
itembonuses.SEResist[e + 1] = effect_value;
}
if (negate_aabonus) {
aabonuses.SEResist[e] = effect_value;
aabonuses.SEResist[e + 1] = effect_value;
}
}
break;
}
+257 -197
View File
@@ -245,8 +245,6 @@ Bot::Bot(
EquipBot();
m_combat_jitter_timer.Start();
if (GetClass() == Class::Rogue) {
m_rogue_evade_timer.Start();
}
@@ -2188,7 +2186,8 @@ void Bot::AI_Process()
}
if (HOLDING || (raid && r_group == RAID_GROUPLESS)) {
TryNonCombatMovementChecks(bot_owner, follow_mob);
glm::vec3 Goal(0, 0, 0);
TryNonCombatMovementChecks(bot_owner, follow_mob, Goal);
return;
}
@@ -2218,6 +2217,8 @@ void Bot::AI_Process()
}
//ALT COMBAT (ACQUIRE HATE)
glm::vec3 Goal(0, 0, 0);
// We have aggro to choose from
if (IsEngaged()) {
if (rest_timer.Enabled()) {
@@ -2286,21 +2287,21 @@ void Bot::AI_Process()
// COMBAT RANGE CALCS
bool front_mob = InFrontMob(tar, GetX(), GetY());
bool behind_mob = BehindMob(tar, GetX(), GetY());
bool stop_melee_level = GetLevel() >= GetStopMeleeLevel();
uint8 stop_melee_level = GetStopMeleeLevel();
tar_distance = sqrt(tar_distance); // sqrt this for future calculations
// Item variables
const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary);
const EQ::ItemInstance* s_item = GetBotItem(EQ::invslot::slotSecondary);
CombatRangeInput i = {
.target = tar,
.target_distance = tar_distance,
.stop_melee_level = stop_melee_level,
.p_item = p_item,
.s_item = s_item
CombatRangeInput input = {
.target = tar,
.target_distance = tar_distance,
.stop_melee_level = stop_melee_level,
.p_item = p_item,
.s_item = s_item
};
CombatRangeOutput o = EvaluateCombatRange(i);
CombatRangeOutput o = EvaluateCombatRange(input);
// Combat range variables
bool at_combat_range = o.at_combat_range;
@@ -2313,7 +2314,7 @@ void Bot::AI_Process()
if (PULLING_BOT || RETURNING_BOT) {
if (!TargetValidation(tar)) { return; }
if (RuleB(Bots, BotsRequireLoS) && !HasLoS()) {
if (!DoLosChecks(tar)) {
return;
}
@@ -2361,7 +2362,7 @@ void Bot::AI_Process()
}
}
TryPursueTarget(leash_distance);
TryPursueTarget(leash_distance, Goal);
return;
}
@@ -2384,30 +2385,52 @@ void Bot::AI_Process()
(bot_owner->GetBotPulling() && NOT_RETURNING_BOT);
if (!other_bot_pulling && at_combat_range) {
CombatPositioningInput cpi {
.tar = tar,
.stop_melee_level = stop_melee_level,
.tar_distance = tar_distance,
.melee_distance_min = melee_distance_min,
.melee_distance = melee_distance,
.melee_distance_max = melee_distance_max,
.behind_mob = behind_mob,
.front_mob = front_mob
};
bool jitter_cooldown = false;
if (m_combat_jitter_timer.GetRemainingTime() > 1 && m_combat_jitter_timer.Enabled()) {
jitter_cooldown = true;
}
if (
IsMoving() ||
GetCombatJitterFlag() ||
GetCombatOutOfRangeJitterFlag()
) {
if (
!GetCombatJitterFlag() ||
!IsMoving() ||
GetCombatOutOfRangeJitterFlag()
) {
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
}
if (DoCombatPositioning(cpi) && IsMoving()) {
return;
}
if (!IsSitting() && !IsFacingMob(tar)) {
FaceTarget(tar);
if (
!jitter_cooldown &&
AI_movement_timer->Check() &&
(!spellend_timer.Enabled() || GetClass() == Class::Bard)
) {
DoCombatPositioning(tar, Goal, stop_melee_level, tar_distance, melee_distance_min, melee_distance, melee_distance_max, behind_mob, front_mob);
return;
}
else {
if (!IsSitting() && !IsFacingMob(tar)) {
FaceTarget(tar);
return;
}
}
if (!IsBotNonSpellFighter() && AI_HasSpells() && AI_EngagedCastCheck()) {
return;
}
if (IsMoving()) {
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
return;
}
if (
!tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
IsBotRanged() &&
@@ -2419,7 +2442,7 @@ void Bot::AI_Process()
ranged_timer.Start();
}
else if (!IsBotRanged() && !stop_melee_level) {
else if (!IsBotRanged() && GetLevel() < stop_melee_level) {
if (
IsTaunting() ||
!GetMaxMeleeRange() ||
@@ -2456,7 +2479,7 @@ void Bot::AI_Process()
// ENGAGED NOT AT COMBAT RANGE
else if (!other_bot_pulling && !TryPursueTarget(leash_distance)) {
else if (!other_bot_pulling && !TryPursueTarget(leash_distance, Goal)) {
return;
}
@@ -2469,7 +2492,7 @@ void Bot::AI_Process()
TryMeditate();
}
else { // Out-of-combat behavior
DoOutOfCombatChecks(bot_owner, follow_mob, leash_distance, fm_distance);
DoOutOfCombatChecks(bot_owner, follow_mob, Goal, leash_distance, fm_distance);
}
}
@@ -2486,10 +2509,8 @@ bool Bot::TryBardMovementCasts() {// Basically, bard bots get a chance to cast i
return false;
}
bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob) {// Non-engaged movement checks
bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal) {// Non-engaged movement checks
if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == Class::Bard)) {
glm::vec3 Goal(0, 0, 0);
if (GUARDING) {
Goal = GetGuardPoint();
}
@@ -2543,7 +2564,7 @@ bool Bot::TryIdleChecks(float fm_distance) {
return false;
}
void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance) {
void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance) {
SetAttackFlag(false);
SetCombatRoundForAlerts(false);
SetAttackingFlag(false);
@@ -2576,7 +2597,7 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_di
}
// Ok to idle
if (TryNonCombatMovementChecks(bot_owner, follow_mob)) {
if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) {
return;
}
@@ -2787,14 +2808,15 @@ bool Bot::TryMeditate() {
}
// This code actually gets processed when we are too far away from target and have not engaged yet
bool Bot::TryPursueTarget(float leash_distance) {
bool Bot::TryPursueTarget(float leash_distance, glm::vec3& Goal) {
if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) {
if (GetTarget() && !IsRooted()) {
LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName());
glm::vec3 Goal = GetTarget()->GetPosition();
Goal = GetTarget()->GetPosition();
if (DistanceSquared(m_Position, Goal) <= leash_distance) {
RunTo(Goal.x, Goal.y, Goal.z);
SetCombatOutOfRangeJitter();
} else {
WipeHateList();
SetTarget(nullptr);
@@ -3106,10 +3128,11 @@ 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 * 0.25f;
o.melee_distance = o.melee_distance_max * 0.45f;
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerTauntingMeleeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperTauntingMeleeDistanceMultiplier);
}
else if (IsBotRanged()) { // Archers/Throwers
float min_distance = RuleI(Combat, MinRangedAttackDist);
@@ -3117,22 +3140,22 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
float desired_range = GetBotDistanceRanged();
max_distance = (max_distance == 0 ? desired_range : max_distance); // stay ranged even if items/ammo aren't correct
o.melee_distance_min = std::max(min_distance, (desired_range * 0.75f));
o.melee_distance_min = std::max(min_distance, (desired_range / 2));
o.melee_distance = std::min(max_distance, desired_range);
}
else if (input.stop_melee_level) { // Casters
else if (is_stop_melee_level) { // Casters
float desired_range = GetBotDistanceRanged();
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range * 0.75f));
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range / 2));
o.melee_distance = std::max((o.melee_distance_max * 1.25f), desired_range);
}
else if (GetMaxMeleeRange()) { // Melee bots set to max melee range
o.melee_distance_min = o.melee_distance_max * 0.80f;
o.melee_distance = o.melee_distance_max * 0.95f;
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMaxMeleeRangeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMaxMeleeRangeDistanceMultiplier);
}
else { // Regular melee
o.melee_distance_min = o.melee_distance_max * 0.30f;
o.melee_distance = o.melee_distance_max * 0.65f;
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMeleeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMeleeDistanceMultiplier);
}
o.at_combat_range = (input.target_distance <= o.melee_distance);
@@ -3153,17 +3176,14 @@ 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() && !HasLoS()) ||
(!GetAttackingFlag() && !CheckLosCheat(tar) && !leash_owner->CheckLosCheat(tar)) ||
!IsAttackAllowed(tar)
) {
invalid_target_state = true;
@@ -9514,14 +9534,7 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool precheck
return false;
}
if (
!BotHasEnoughMana(spell_id) &&
(
!RuleB(Bots, FinishBuffing) ||
IsEngaged() ||
IsBotBuffSpellType(spell_type)
)
) {
if (!BotHasEnoughMana(spell_id)) {
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !BotHasEnoughMana.'", GetCleanName(), GetSpellName(spell_id));
return false;
}
@@ -9716,7 +9729,7 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool precheck
)
)
&&
tar->CanBuffStack(spell_id, GetLevel(), true) < 0
tar->CanBuffStack(spell_id, GetLevel(), false) < 0
) {
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanBuffStack.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName());
return false;
@@ -10577,8 +10590,6 @@ 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
}
}
@@ -10630,7 +10641,7 @@ int Bot::GetDefaultSetting(uint16 setting_category, uint16 setting_type, uint8 s
case BotSettingCategories::SpellTypeAnnounceCast:
return GetDefaultSpellTypeAnnounceCast(setting_type, stance);
default:
return 0; // Default return value for unrecognized categories
break;
}
}
@@ -10669,7 +10680,7 @@ int Bot::GetSetting(uint16 setting_category, uint16 setting_type) {
case BotSettingCategories::SpellTypeAnnounceCast:
return GetSpellTypeAnnounceCast(setting_type);
default:
return 0; // Default return value for unrecognized categories
break;
}
}
@@ -11396,7 +11407,7 @@ bool Bot::AttemptForcedCastSpell(Mob* tar, uint16 spell_id, bool is_disc) {
return false;
}
if (!HasLoS() && !DoLosChecks(tar)) {
if (!DoLosChecks(tar)) {
return false;
}
@@ -11820,7 +11831,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
return false;
case BotSpellTypes::ResistBuffs:
case BotSpellTypes::PetResistBuffs:
if (IsResistanceBuffSpell(spell_id) && !IsEffectInSpell(spell_id, SE_DamageShield)) {
if (IsResistanceBuffSpell(spell_id)) {
return true;
}
@@ -11944,171 +11955,216 @@ void Bot::SetCastedSpellType(uint16 spell_type) {
_castedSpellType = spell_type;
}
void Bot::DoFaceCheckWithJitter(Mob* tar) {
if (!tar) {
return;
}
if (IsMoving()) {
return;
}
SetCombatJitter();
if (!IsFacingMob(tar)) {
FaceTarget(tar);
return;
}
return;
}
void Bot::DoFaceCheckNoJitter(Mob* tar) {
if (!tar) {
return;
}
if (IsMoving()) {
return;
}
if (!IsFacingMob(tar)) {
FaceTarget(tar);
return;
}
return;
}
void Bot::RunToGoalWithJitter(glm::vec3 Goal) {
RunTo(Goal.x, Goal.y, Goal.z);
SetCombatJitter();
}
void Bot::SetCombatJitter() {
void Bot::SetCombatOutOfRangeJitter() {
SetCombatOutOfRangeJitterFlag();
if (RuleI(Bots, MaxJitterTimer) > 0) {
m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
}
}
bool Bot::DoCombatPositioning(const CombatPositioningInput& input)
{
bool adjustment_needed = false;
bool is_too_close = input.tar_distance < input.melee_distance_min;
bool los_adjust = !HasRequiredLoSForPositioning(input.tar);
bool behind_mob_set = !input.stop_melee_level &&
!IsBotRanged() &&
GetBehindMob(); // Don't want casters or ranged to find positions behind the target.
bool adjustment_allowed = !IsMoving() &&
m_combat_jitter_timer.Check() &&
(!spellend_timer.Enabled() || GetClass() == Class::Bard);
void Bot::SetCombatJitter() {
SetCombatJitterFlag();
if (!IsMoving() && !IsSitting() && !IsFacingMob(input.tar)) {
FaceTarget(input.tar);
if (RuleI(Bots, MaxJitterTimer) > 0) {
m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
}
FindPositionInput find_position_input = {
.tar = input.tar,
.distance_min = input.melee_distance_min,
.distance_max = input.melee_distance_max,
.behind_only = behind_mob_set,
.front_only = IsTaunting(),
.bypass_los = false,
};
bool is_melee = (!input.stop_melee_level && !IsBotRanged());
if (input.tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range
adjustment_needed =
(input.tar_distance <= input.melee_distance_max) &&
HasTargetReflection();
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_max + 1;
find_position_input.distance_max = input.melee_distance_max * 1.25f;
PlotBotPositionAroundTarget(find_position_input);
}
} else {
if (input.tar->IsFeared()) {
adjustment_needed = los_adjust;
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_min;
find_position_input.distance_max = input.melee_distance;
find_position_input.behind_only = false;
find_position_input.front_only = false;
PlotBotPositionAroundTarget(find_position_input);
}
}
else if (IsTaunting() || HasTargetReflection()) { // Taunting/Aggro adjustments
adjustment_needed =
is_too_close ||
los_adjust ||
(is_melee && !input.front_mob);
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_min;
find_position_input.distance_max = input.melee_distance;
find_position_input.behind_only = false;
find_position_input.front_only = true;
PlotBotPositionAroundTarget(find_position_input);
}
} else {
if (input.tar->IsEnraged() && is_melee) { // Move non-taunting melee bots behind target during enrage
adjustment_needed =
!behind_mob_set ||
is_too_close ||
los_adjust;
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_min;
find_position_input.distance_max = input.melee_distance;
find_position_input.behind_only = true;
find_position_input.front_only = false;
PlotBotPositionAroundTarget(find_position_input);
}
} else { // Regular adjustments
adjustment_needed =
is_too_close ||
los_adjust ||
(behind_mob_set && !input.behind_mob);
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_min;
find_position_input.distance_max = input.melee_distance;
PlotBotPositionAroundTarget(find_position_input);
}
}
}
}
if (!adjustment_needed && IsMoving()) {
StopMoving();
}
return adjustment_needed;
}
bool Bot::PlotBotPositionAroundTarget(const FindPositionInput& input) {
void Bot::DoCombatPositioning(
Mob* tar,
glm::vec3 Goal,
bool stop_melee_level,
float tar_distance,
float melee_distance_min,
float melee_distance,
float melee_distance_max,
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);
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;
}
}
if (IsTaunting()) { // Taunting adjustments
Mob* mob_tar = tar->GetTarget();
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();
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();
RunToGoalWithJitter(Goal);
return;
}
}
}
}
}
}
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) {
bool Result = false;
if (input.tar) {
glm::vec3 temp_goal(0, 0, input.tar->GetZ());
glm::vec3 tar_position(input.tar->GetX(), input.tar->GetY(), input.tar->GetZ());
float look_heading = 0;
float best_z = 0;
float tar_distance = 0;
float desired_angle = 0;
const float offset = GetZOffset();
const uint16 max_iterations_allowed = 50;
uint16 counter = 0;
if (target) {
float look_heading = 0;
min_distance = min_distance;
max_distance = max_distance;
float temp_x = 0;
float temp_y = 0;
float temp_z = target->GetZ();
float best_z = 0;
auto offset = GetZOffset();
const float tar_x = target->GetX();
const float tar_y = target->GetY();
float tar_distance = 0;
glm::vec3 temp_z_Position;
glm::vec4 temp_m_Position;
const uint16 max_iterations_allowed = 50;
uint16 counter = 0;
while (counter < max_iterations_allowed) {
temp_goal.x = tar_position.x + zone->random.Real(-input.distance_max, input.distance_max);
temp_goal.y = tar_position.y + zone->random.Real(-input.distance_max, input.distance_max);
best_z = GetFixedZ(temp_goal);
temp_x = tar_x + zone->random.Real(-max_distance, max_distance);
temp_y = tar_y + zone->random.Real(-max_distance, max_distance);
temp_z_Position.x = temp_x;
temp_z_Position.y = temp_y;
temp_z_Position.z = temp_z;
best_z = GetFixedZ(temp_z_Position);
if (best_z != BEST_Z_INVALID) {
temp_goal.z = best_z;
temp_z = best_z;
}
else {
counter++;
continue;
}
tar_distance = Distance(input.tar->GetPosition(), temp_goal);
temp_m_Position.x = temp_x;
temp_m_Position.y = temp_y;
temp_m_Position.z = temp_z;
if (tar_distance > input.distance_max || tar_distance < std::max(input.distance_min, (input.distance_max * 0.75f))) {
tar_distance = Distance(target->GetPosition(), temp_m_Position);
if (tar_distance > max_distance || tar_distance < min_distance) {
counter++;
continue;
}
if (input.front_only && !InFrontMob(input.tar, temp_goal.x, temp_goal.y)) {
if (front_only && !InFrontMob(target, temp_x, temp_y)) {
counter++;
continue;
}
else if (input.behind_only && !BehindMob(input.tar, temp_goal.x, temp_goal.y)) {
else if (behind_only && !BehindMob(target, temp_x, temp_y)) {
counter++;
continue;
}
if (!input.bypass_los && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(input.tar, temp_goal.x, temp_goal.y, temp_goal.z)) {
if (!bypass_los && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(target, temp_x, temp_y, temp_z)) {
counter++;
continue;
}
@@ -12117,7 +12173,9 @@ bool Bot::PlotBotPositionAroundTarget(const FindPositionInput& input) {
}
if (Result) {
RunToGoalWithJitter(temp_goal);
x_dest = temp_x;
y_dest = temp_y;
z_dest = temp_z;
}
}
@@ -12139,11 +12197,7 @@ bool Bot::RequiresLoSForPositioning() {
}
for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) {
if (IsHealBotSpellType(i) || i == BotSpellTypes::PBAENuke) {
continue;
}
if (!GetSpellTypeHold(i)) {
if (IsBotSpellTypeDetrimental(i) && !GetSpellTypeHold(i)) {
return true;
}
}
@@ -12156,7 +12210,7 @@ bool Bot::HasRequiredLoSForPositioning(Mob* tar) {
return true;
}
if (RequiresLoSForPositioning() && !HasLoS()) {
if (RequiresLoSForPositioning() && !DoLosChecks(tar)) {
return false;
}
@@ -12171,6 +12225,10 @@ 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)) {
@@ -12262,6 +12320,8 @@ bool Bot::HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob*
return false;
}
SetHasLoS(true);
return true;
}
+32 -5
View File
@@ -39,6 +39,9 @@
constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds
constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MIN = 1500; // 1.5 seconds
constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MAX = 3000; // 3 seconds
constexpr uint32 MAG_EPIC_1_0 = 28034;
extern WorldServer worldserver;
@@ -229,6 +232,21 @@ static std::map<uint16, std::string> botSubType_names = {
{ CommandedSubTypes::Selo, "Selo" }
};
struct CombatRangeInput {
Mob* target;
float target_distance;
uint8 stop_melee_level;
const EQ::ItemInstance* p_item;
const EQ::ItemInstance* s_item;
};
struct CombatRangeOutput {
bool at_combat_range = false;
float melee_distance_min = 0.0f;
float melee_distance = 0.0f;
float melee_distance_max = 0.0f;
};
class Bot : public NPC {
friend class Mob;
public:
@@ -559,7 +577,7 @@ public:
uint16 GetPetBotSpellType(uint16 spell_type);
// Movement checks
bool PlotBotPositionAroundTarget(const FindPositionInput& input);
bool PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behind_only = false, bool front_only = false, bool bypass_los = false);
std::vector<Mob*> GetSpellTargetList(bool entire_raid = false);
void SetSpellTargetList(std::vector<Mob*> spell_target_list) { _spell_target_list = spell_target_list; }
std::vector<Mob*> GetGroupSpellTargetList() { return _group_spell_target_list; }
@@ -1084,8 +1102,15 @@ public:
bool CheckIfCasting(float fm_distance);
void HealRotationChecks();
bool GetCombatJitterFlag() { return m_combat_jitter_flag; }
void SetCombatJitterFlag(bool flag = true) { m_combat_jitter_flag = flag; }
bool GetCombatOutOfRangeJitterFlag() { return m_combat_out_of_range_jitter_flag; }
void SetCombatOutOfRangeJitterFlag(bool flag = true) { m_combat_out_of_range_jitter_flag = flag; }
void SetCombatJitter();
bool DoCombatPositioning(const CombatPositioningInput& input);
void SetCombatOutOfRangeJitter();
void DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stop_melee_level, float tar_distance, float melee_distance_min, float melee_distance, float melee_distance_max, bool behind_mob, bool front_mob);
void DoFaceCheckWithJitter(Mob* tar);
void DoFaceCheckNoJitter(Mob* tar);
void RunToGoalWithJitter(glm::vec3 Goal);
bool RequiresLoSForPositioning();
bool HasRequiredLoSForPositioning(Mob* tar);
@@ -1093,12 +1118,12 @@ public:
// Try Combat Methods
bool TryEvade(Mob* tar);
bool TryFacingTarget(Mob* tar);
bool TryPursueTarget(float leash_distance);
bool TryPursueTarget(float leash_distance, glm::vec3& Goal);
bool TryMeditate();
bool TryAutoDefend(Client* bot_owner, float leash_distance);
bool TryIdleChecks(float fm_distance);
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob);
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance);
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal);
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance);
bool TryBardMovementCasts();
bool BotRangedAttack(Mob* other, bool can_double_attack = false);
bool CheckDoubleRangedAttack();
@@ -1164,6 +1189,8 @@ private:
Timer m_auto_save_timer;
Timer m_combat_jitter_timer;
bool m_combat_jitter_flag;
bool m_combat_out_of_range_jitter_flag;
bool m_dirtyautohaters;
bool m_guard_flag;
+1 -1
View File
@@ -22,7 +22,7 @@ void bot_command_attack(Client *c, const Seperator *sep)
return;
}
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) {
if (!c->DoLosChecks(target_mob)) {
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
return;
}
+1 -8
View File
@@ -525,9 +525,6 @@ 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;
}
@@ -546,9 +543,6 @@ 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;
@@ -562,8 +556,7 @@ 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;
+1 -7
View File
@@ -48,10 +48,9 @@ void bot_command_click_item(Client* c, const Seperator* sep)
sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end());
Mob* tar = c->GetTarget();
bool is_success = false;
for (auto my_bot : sbl) {
if (!my_bot->ValidStateCheck(c)) {
if (my_bot->BotPassiveCheck()) {
continue;
}
@@ -69,11 +68,6 @@ void bot_command_click_item(Client* c, const Seperator* sep)
continue;
}
is_success = true;
my_bot->TryItemClick(slot_id);
}
if (!is_success) {
c->Message(Chat::Yellow, "None of your bots are capable of doing that currently.");
}
}
+1 -5
View File
@@ -197,11 +197,7 @@ void bot_command_depart(Client* c, const Seperator* sep)
bot_iter->SetCommandedSpell(true);
if (!IsValidSpell(itr->SpellId)) {
continue;
}
if (BotRequiresLoSToCast(BotSpellTypes::Teleport, itr->SpellId) && !bot_iter->HasLoS()) {
if (!IsValidSpellAndLoS(itr->SpellId, bot_iter->HasLoS())) {
continue;
}
+1 -1
View File
@@ -18,7 +18,7 @@ void bot_command_precombat(Client* c, const Seperator* sep)
return;
}
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(c->GetTarget())) {
if (!c->DoLosChecks(c->GetTarget())) {
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
return;
+1 -1
View File
@@ -48,7 +48,7 @@ void bot_command_pull(Client *c, const Seperator *sep)
return;
}
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) {
if (!c->DoLosChecks(target_mob)) {
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
return;
-36
View File
@@ -21,7 +21,6 @@
#include "../common/types.h"
#include "../common/timer.h"
#include "mob.h"
#include <sstream>
@@ -152,39 +151,4 @@ struct BotSpellTypesByClass {
std::string description;
};
struct CombatRangeInput {
Mob* target;
float target_distance;
bool stop_melee_level;
const EQ::ItemInstance* p_item;
const EQ::ItemInstance* s_item;
};
struct CombatRangeOutput {
bool at_combat_range = false;
float melee_distance_min = 0.0f;
float melee_distance = 0.0f;
float melee_distance_max = 0.0f;
};
struct CombatPositioningInput {
Mob* tar;
bool stop_melee_level;
float tar_distance;
float melee_distance_min;
float melee_distance;
float melee_distance_max;
bool behind_mob;
bool front_mob;
};
struct FindPositionInput {
Mob* tar;
float distance_min;
float distance_max;
bool behind_only;
bool front_only;
bool bypass_los;
};
#endif // BOT_STRUCTS
+71 -89
View File
@@ -41,10 +41,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
return false;
}
if (
!IsCommandedSpell() &&
zone->random.Int(0, 100) > chance
) {
if (chance < 100 && zone->random.Int(0, 100) > chance) {
return false;
}
@@ -64,7 +61,10 @@ 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) {
if (BotSpellTypeRequiresLoS(spell_type) && tar != this) {
SetHasLoS(DoLosChecks(tar));
}
else {
SetHasLoS(true);
}
@@ -218,11 +218,8 @@ 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 (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) {
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
continue;
}
@@ -276,11 +273,7 @@ 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 (!IsValidSpell(s.SpellId)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) {
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
continue;
}
@@ -288,7 +281,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) {
continue;
return false;
}
tar = add_mob;
@@ -334,11 +327,7 @@ bool Bot::BotCastCure(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spe
bot_spell = GetBestBotSpellForCure(this, tar, spell_type);
if (!IsValidSpell(bot_spell.SpellId)) {
return false;
}
if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) {
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
return false;
}
@@ -408,11 +397,7 @@ bool Bot::BotCastPet(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
bot_spell = GetFirstBotSpellBySpellType(this, spell_type);
}
if (!IsValidSpell(bot_spell.SpellId)) {
return false;
}
if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) {
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
return false;
}
@@ -433,10 +418,6 @@ 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));
@@ -453,25 +434,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 (!IsValidSpell(bot_spell.SpellId)) {
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
return false;
}
}
if (!IsValidSpell(bot_spell.SpellId)) {
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
bot_spell = GetBestBotSpellForNukeByBodyType(this, tar->GetBodyType(), spell_type, IsAEBotSpellType(spell_type), tar);
}
if (spell_type == BotSpellTypes::Nuke && bot_class == Class::Wizard && !IsValidSpell(bot_spell.SpellId)) {
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS()) && spell_type == BotSpellTypes::Nuke && bot_class == Class::Wizard) {
bot_spell = GetBestBotWizardNukeSpellByTargetResists(this, tar, spell_type);
}
if (!IsValidSpell(bot_spell.SpellId)) {
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type));
for (const auto& s : bot_spell_list) {
if (!IsValidSpell(s.SpellId)) {
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
continue;
}
@@ -581,9 +562,7 @@ 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 &&
!IsEngaged() &&
IsBotBuffSpellType(AIBot_spells[i].type)
manaCost > hasMana && AIBot_spells[i].type == BotSpellTypes::Buff
) {
SetMana(manaCost);
}
@@ -929,11 +908,7 @@ 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 (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
continue;
}
@@ -971,11 +946,7 @@ 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 (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
continue;
}
@@ -1016,11 +987,7 @@ 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 (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
continue;
}
@@ -1049,11 +1016,7 @@ 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 (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
continue;
}
@@ -1140,11 +1103,7 @@ 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 (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
continue;
}
@@ -1176,6 +1135,7 @@ 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;
@@ -1201,6 +1161,7 @@ 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;
@@ -1225,6 +1186,7 @@ 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;
@@ -1281,6 +1243,7 @@ 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;
@@ -1305,6 +1268,7 @@ 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;
@@ -1334,6 +1298,7 @@ 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;
@@ -1372,6 +1337,7 @@ 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;
@@ -1410,6 +1376,7 @@ 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;
@@ -1443,6 +1410,7 @@ 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)
@@ -1465,10 +1433,11 @@ 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);
bool is_pbae_spell = IsPBAESpell(spell_id);
int buff_count = 0;
NPC* npc = nullptr;
for (auto& close_mob : caster->m_close_mobs) {
buff_count = 0;
npc = close_mob.second->CastToNPC();
if (!npc) {
@@ -1479,29 +1448,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 (spell_ae_range < Distance(npc->GetPosition(), m->GetPosition())) {
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 (caster->CastChecks(spell_id, m, spell_type, true, true)) {
@@ -1517,6 +1486,11 @@ 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 {
@@ -1528,10 +1502,18 @@ 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;
}
}
@@ -1552,6 +1534,7 @@ 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) &&
@@ -1733,6 +1716,7 @@ 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;
@@ -1745,6 +1729,7 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType ta
continue;
}
if (
caster->IsCommandedSpell() ||
!AE ||
@@ -1781,6 +1766,7 @@ 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;
@@ -1844,6 +1830,7 @@ 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;
}
@@ -1900,6 +1887,8 @@ 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;
@@ -1937,11 +1926,7 @@ 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 (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
continue;
}
@@ -1987,11 +1972,7 @@ 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 (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
continue;
}
@@ -2110,6 +2091,7 @@ 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:
@@ -2175,8 +2157,6 @@ 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);
}
@@ -2841,6 +2821,7 @@ 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)
@@ -2868,6 +2849,7 @@ 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)
@@ -1,13 +1,25 @@
#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::TestDataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
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)
{
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,6 +36,19 @@ 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) {
@@ -62,7 +75,7 @@ std::string SerializeHandin(const std::map<std::string, uint32> &items, const Ha
return j.dump();
}
void ZoneCLI::TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
void ZoneCLI::NpcHandins(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::TestNpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description)
void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description)
{
if (cmd[{"-h", "--help"}]) {
return;
-57
View File
@@ -1,57 +0,0 @@
#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();
}
File diff suppressed because it is too large Load Diff
+2 -66
View File
@@ -882,13 +882,9 @@ void Client::SendZoneInPackets()
//SendGuildMembers();
SendGuildURL();
SendGuildChannel();
if (RuleB(Guild, EnableLFGuild)) {
SendGuildLFGuildStatus();
}
}
if (RuleB(Guild, EnableLFGuild)) {
SendLFGuildStatus();
SendGuildLFGuildStatus();
}
SendLFGuildStatus();
//No idea why live sends this if even were not in a guild
SendGuildMOTD();
@@ -995,8 +991,6 @@ bool Client::Save(uint8 iCommitNow) {
if(!ClientDataLoaded())
return false;
BenchTimer timer;
/* Wrote current basics to PP for saves */
if (!m_lock_save_position) {
m_pp.x = m_Position.x;
@@ -1024,8 +1018,6 @@ bool Client::Save(uint8 iCommitNow) {
m_pp.endurance = current_endurance;
}
database.TransactionBegin();
/* Save Character Currency */
database.SaveCharacterCurrency(CharacterID(), &m_pp);
@@ -1109,10 +1101,6 @@ bool Client::Save(uint8 iCommitNow) {
database.botdb.SaveBotSettings(this);
}
database.TransactionCommit();
LogInfo("Save for [{}] took [{}]", GetCleanName(), timer.elapsed());
return true;
}
@@ -1217,58 +1205,6 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s
LogDebug("Client::ChannelMessageReceived() Channel:[{}] message:[{}]", chan_num, message);
if (RuleB(Chat, AlwaysCaptureCommandText)) {
if (message[0] == COMMAND_CHAR) {
if (command_dispatch(this, message, false) == -2) {
if (parse->PlayerHasQuestSub(EVENT_COMMAND)) {
int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0);
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Command '%s' not recognized.", message);
}
}
else if (parse->PlayerHasQuestSub(EVENT_SAY)) {
int i = parse->EventPlayer(EVENT_SAY, this, message, 0);
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Command '%s' not recognized.", message);
}
}
else {
if (!RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Command '%s' not recognized.", message);
}
}
}
return;
}
if (message[0] == BOT_COMMAND_CHAR) {
if (RuleB(Bots, Enabled)) {
if (bot_command_dispatch(this, message) == -2) {
if (parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) {
int i = parse->EventPlayer(EVENT_BOT_COMMAND, this, message, 0);
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Bot command '%s' not recognized.", message);
}
}
else if (parse->PlayerHasQuestSub(EVENT_SAY)) {
int i = parse->EventPlayer(EVENT_SAY, this, message, 0);
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Bot command '%s' not recognized.", message);
}
}
else {
if (!RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Bot command '%s' not recognized.", message);
}
}
}
} else {
Message(Chat::Red, "Bots are disabled on this server.");
}
return;
}
}
if (targetname == nullptr) {
targetname = (!GetTarget()) ? "" : GetTarget()->GetName();
}
-3
View File
@@ -483,9 +483,6 @@ public:
virtual bool Save() { return Save(0); }
bool Save(uint8 iCommitNow); // 0 = delayed, 1=async now, 2=sync now
inline void SaveCharacterData() {
database.SaveCharacterData(this, &m_pp, &m_epp);
};
/* New PP Save Functions */
bool SaveCurrency(){ return database.SaveCharacterCurrency(this->CharacterID(), &m_pp); }
+32 -34
View File
@@ -120,46 +120,48 @@ 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;
if (!zones_list.empty()) {
auto it = std::find(zones_list.begin(), zones_list.end(), std::to_string(zone->GetZoneID()));
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 (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());
}
break;
}
++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;
if (!zones_forced_list.empty()) {
auto it = std::find(zones_forced_list.begin(), zones_forced_list.end(), std::to_string(zone->GetZoneID()));
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 (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());
}
break;
}
++i;
}
catch (const std::exception& e) {
LogInfo("Invalid entry in Rule VegasScaling:SpecialScalingZones or SpecialScalingZonesVersions: [{}]", e.what());
}
}
@@ -256,8 +258,6 @@ 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,8 +271,6 @@ 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
}
}
+9 -13
View File
@@ -73,7 +73,6 @@ 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;
@@ -914,14 +913,11 @@ void Client::CompleteConnect()
SendDynamicZoneUpdates();
// 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;
}
/** Request adventure info **/
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);
@@ -7984,7 +7980,7 @@ void Client::Handle_OP_GuildCreate(const EQApplicationPacket *app)
}
SetGuildID(new_guild_id);
UpdateWho();
SendGuildList();
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();
@@ -8153,7 +8149,7 @@ void Client::Handle_OP_GuildInvite(const EQApplicationPacket *app)
if (!invitee) {
Message(
Chat::Red,
"Prospective guild member %s must be in zone to perform guild operations on them.",
"Prospective guild member %s must be in zone to preform guild operations on them.",
gc->othername
);
return;
@@ -11066,7 +11062,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
if (!target)
break;
if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(target)) {
if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(target) || !CheckLosCheat(target))) {
mypet->SayString(this, NOT_LEGAL_TARGET);
break;
}
@@ -11134,7 +11130,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
break;
}
if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(GetTarget())) {
if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(GetTarget()) || !CheckLosCheat(GetTarget()))) {
mypet->SayString(this, NOT_LEGAL_TARGET);
break;
}
-2
View File
@@ -217,8 +217,6 @@ bool Client::Process() {
GetMerc()->Depop();
}
instalog = true;
camp_timer.Disable();
}
if (IsStunned() && stunned_timer.Check())
+1 -1
View File
@@ -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", "[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("zoneshutdown", "[shortname] - Shut down a zone server", AccountStatus::GMLeadAdmin, command_zoneshutdown) ||
command_add("zsave", " Saves zheader to the database", AccountStatus::QuestTroupe, command_zsave)
) {
command_deinit();
+5
View File
@@ -111,6 +111,7 @@ 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);
@@ -138,6 +139,7 @@ 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);
@@ -145,6 +147,7 @@ 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);
@@ -198,6 +201,8 @@ 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
View File
@@ -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, CorpseOwnerOnlineCheckTime) * 1000);
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineTime));
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, CorpseOwnerOnlineCheckTime) * 1000);
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineTime));
m_check_rezzable_timer.SetTimer(1000);
m_corpse_rezzable_timer.Disable();
+1 -3
View File
@@ -1,7 +1,6 @@
#include "data_bucket.h"
#include "zonedb.h"
#include "mob.h"
#include "client.h"
#include "worldserver.h"
#include <ctime>
#include <cctype>
@@ -360,8 +359,7 @@ bool DataBucket::GetDataBuckets(Mob *mob)
BulkLoadEntitiesToCache(DataBucketLoadType::Bot, {id});
}
else if (mob->IsClient()) {
uint32 account_id = mob->CastToClient()->AccountID();
BulkLoadEntitiesToCache(DataBucketLoadType::Account, {account_id});
BulkLoadEntitiesToCache(DataBucketLoadType::Account, {id});
BulkLoadEntitiesToCache(DataBucketLoadType::Client, {id});
}
-68
View File
@@ -35,9 +35,6 @@
#include <string.h>
#include <glm/ext/matrix_transform.hpp>
#include <numbers>
#define OPEN_DOOR 0x02
#define CLOSE_DOOR 0x03
#define OPEN_INVDOOR 0x03
@@ -973,68 +970,3 @@ 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;
}
-1
View File
@@ -76,7 +76,6 @@ 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; }
+5 -26
View File
@@ -3143,30 +3143,20 @@ 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/bot pets...
if (own && own->IsOfClientBot()) {
//do not depop player's pets...
if (own && own->IsClient())
continue;
}
if (pnpc->IsHorse()) {
if (pnpc->IsHorse())
continue;
}
if (pnpc->IsFindable()) {
if (pnpc->IsFindable())
UpdateFindableNPCState(pnpc, true);
}
// Depop below will eventually remove this npc from the entity list
// but that can happen AFTER we've already tried to spawn its replacement.
// So go ahead and remove it from the limits so it doesn't count.
if (npc_limit_list.count(pnpc->GetID())) {
npc_limit_list.erase(pnpc->GetID());
}
pnpc->WipeHateList();
pnpc->Depop(StartSpawnTimer);
}
}
@@ -5998,14 +5988,3 @@ 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);
}
}
-1
View File
@@ -580,7 +580,6 @@ 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;
-27
View File
@@ -35,26 +35,6 @@ 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());
@@ -564,13 +544,6 @@ 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(
-2
View File
@@ -49,7 +49,6 @@
#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)
{
@@ -111,7 +110,6 @@ 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
+5
View File
@@ -81,6 +81,11 @@ 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(
-16
View File
@@ -1,16 +0,0 @@
#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.");
}
}
+13 -55
View File
@@ -6,18 +6,8 @@ extern WorldServer worldserver;
void command_zoneshutdown(Client *c, const Seperator *sep)
{
const int arguments = sep->argnum;
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]");
if (!arguments) {
c->Message(Chat::White, "Usage: #zoneshutdown [Zone ID|Zone Short Name]");
return;
}
@@ -26,55 +16,23 @@ void command_zoneshutdown(Client *c, const Seperator *sep)
return;
}
uint32 zone_id = 0;
uint16 instance_id = 0;
std::string message = "";
const uint32 zone_id = sep->IsNumber(1) ? Strings::ToUnsignedInt(sep->arg[1]) : ZoneID(sep->arg[1]);
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);
if (!zone_id) {
c->Message(
Chat::White,
fmt::format(
"Zone '{}' does not exist.",
sep->arg[1]
).c_str()
);
return;
}
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->instance_id = instance_id;
s->zone_id = zone_id;
strn0cpy(s->admin_name, c->GetName(), sizeof(s->admin_name));
+1 -1
View File
@@ -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();
+1 -12
View File
@@ -500,9 +500,6 @@ 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);
@@ -618,10 +615,6 @@ int main(int argc, char **argv)
}
}
if (WorldserverProcess.Check()) {
worldserver.Process();
}
if (is_zone_loaded) {
{
entity_list.GroupProcess();
@@ -654,10 +647,7 @@ int main(int argc, char **argv)
InterserverTimer.Start();
database.ping();
content_db.ping();
if (UpdateWhoTimer.Check()) {
UpdateWhoTimer.SetTimer(RuleI(Zone, UpdateWhoTimer) * 1000); // in-case it was changed
entity_list.UpdateWho();
}
entity_list.UpdateWho();
}
};
@@ -678,7 +668,6 @@ int main(int argc, char **argv)
safe_delete(Config);
if (zone != 0) {
zone->SetSaveZoneState(false);
zone->Shutdown(true);
}
//Fix for Linux world server problem.
+54 -41
View File
@@ -1698,6 +1698,13 @@ void Mob::StopMoving()
void Mob::StopMoving(float new_heading)
{
if (IsBot()) {
auto bot = CastToBot();
bot->SetCombatJitterFlag(false);
bot->SetCombatOutOfRangeJitterFlag(false);
}
StopNavigation();
RotateTo(new_heading);
@@ -4614,12 +4621,8 @@ void Mob::SetZone(uint32 zone_id, uint32 instance_id)
{
CastToClient()->GetPP().zone_id = zone_id;
CastToClient()->GetPP().zoneInstance = instance_id;
CastToClient()->SaveCharacterData();
}
if (!IsClient()) {
Save(); // bots or other things might be covered here for some reason
}
Save();
}
void Mob::Kill() {
@@ -8672,54 +8675,56 @@ bool Mob::IsInGroupOrRaid(Mob* other, bool same_raid_group) {
bool Mob::DoLosChecks(Mob* other) {
if (!CheckLosFN(other) || !CheckWaterLoS(other)) {
if (RuleB(Map, EnableLoSCheatExemptions) && CheckLosCheatExempt(other)) {
if (CheckLosCheatExempt(other)) {
return true;
}
return false;
}
if (RuleB(Map, CheckForDoorLoSCheat) && !CheckDoorLoSCheat(other)) {
if (!CheckLosCheat(other)) {
return false;
}
return true;
}
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;
bool Mob::CheckLosCheat(Mob* other) {
if (RuleB(Map, CheckForLoSCheat)) {
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->GetNoKeyring() != 0 ||
d->GetDoorParam() > 0
)
) {
float distance = Distance(m_Position, d->GetPosition());
) {
// 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());
if (distance > RuleR(Map, RangeCheckForDoorLoSCheat) || !CheckLosFN(d->GetX(), d->GetY(), d->GetZ(), GetSize())) {
continue;
if (td) {
if (Strings::RemoveNumbers(d->GetDoorName()) != Strings::RemoveNumbers(td->GetDoorName())) {
continue;
}
}
}
if (d->IsDoorBetween(GetPosition(), other->GetPosition(), d->GetSize())) {
return false;
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;
}
}
}
}
@@ -8728,18 +8733,26 @@ bool Mob::CheckDoorLoSCheat(Mob* other) {
return true;
}
bool Mob::CheckLosCheatExempt(Mob* other) {
glm::vec4 exempt_check_who;
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;
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;
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;
}
*/
}
return false;
+3 -3
View File
@@ -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 CheckDoorLoSCheat(Mob* other); //door skipping checks for LoS
bool CheckLosCheat(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; }
@@ -962,7 +962,7 @@ public:
uint16 GetSympatheticFocusEffect(focusType type, uint16 spell_id);
bool TryFadeEffect(int slot);
void DispelMagic(Mob* casterm, uint16 spell_id, int effect_value);
bool TrySpellEffectResist(uint16 spell_id);
uint16 GetSpellEffectResistChance(uint16 spell_id);
int32 GetVulnerability(Mob *caster, uint32 spell_id, uint32 ticsremaining, bool from_buff_tic = false);
int64 GetFcDamageAmtIncoming(Mob *caster, int32 spell_id, bool from_buff_tic = false);
int64 GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spell_id); //**** This can be removed when bot healing focus code is updated ****
@@ -1125,7 +1125,7 @@ public:
virtual void SetAttackTimer();
inline void SetInvul(bool invul) { invulnerable=invul; }
inline bool GetInvul() { return invulnerable; }
inline bool GetInvul(void) { return invulnerable; }
void SetExtraHaste(int haste, bool need_to_save = true);
inline int GetExtraHaste() { return extra_haste; }
virtual int GetHaste();
+9 -4
View File
@@ -933,11 +933,16 @@ void MobMovementManager::SendCommandToClients(
float MobMovementManager::FixHeading(float in)
{
int h = static_cast<int>(in) % 512;
if (h < 0) {
h += 512;
auto h = in;
while (h > 512.0) {
h -= 512.0;
}
return static_cast<float>(h);
while (h < 0.0) {
h += 512.0;
}
return h;
}
void MobMovementManager::DumpStats(Client *client)
+35
View File
@@ -132,6 +132,8 @@ 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),
@@ -622,6 +624,28 @@ 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();
@@ -630,6 +654,17 @@ 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);
}
+10 -6
View File
@@ -603,9 +603,10 @@ public:
// zone state save
inline void SetQueuedToCorpse() { m_queued_for_corpse = true; }
inline bool IsQueuedForCorpse() const { return m_queued_for_corpse; }
inline bool IsQueuedForCorpse() { return m_queued_for_corpse; }
inline uint32_t SetCorpseDecayTime(uint32_t decay_time) { return m_corpse_decay_time = decay_time; }
inline void SetResumedFromZoneSuspend(bool state = true) { m_resumed_from_zone_suspend = state; }
inline bool IsResumedFromZoneSuspend() const { return m_resumed_from_zone_suspend; }
inline bool IsResumedFromZoneSuspend() { return m_resumed_from_zone_suspend; }
inline void LoadBuffsFromState(std::vector<Buffs_Struct> in_buffs) {
int i = 0;
@@ -657,12 +658,15 @@ 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
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 = {};
// this is a timer that protects a NPC from having double assignment of loot
// this is a 30-second 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 a short time
// if loot was to be assigned via script again, this protects double assignment for 30 seconds
Timer m_resumed_from_zone_suspend_shutoff_timer = {};
std::list<NpcFactionEntriesRepository::NpcFactionEntries> faction_list;
+1 -2
View File
@@ -699,7 +699,7 @@ void QuestManager::stoptimer(const std::string& timer_name, Mob* m)
}
for (auto e = QTimerList.begin(); e != QTimerList.end(); ++e) {
if (e->mob && e->mob == m && e->name == timer_name) {
if (e->mob && e->mob == m) {
parse->EventMob(EVENT_TIMER_STOP, m, nullptr, [&]() { return timer_name; });
QTimerList.erase(e);
@@ -3506,7 +3506,6 @@ 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);
+6 -16
View File
@@ -191,20 +191,16 @@ 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 (m_resumed_npc_id > 0) {
npcid = m_resumed_npc_id;
m_resumed_npc_id = 0;
if (RuleB(Zone, StateSavingOnShutdown) && currentnpcid && currentnpcid > 0) {
npcid = currentnpcid;
} else {
npcid = spawn_group->GetNPCType(condition_value);
}
@@ -277,13 +273,7 @@ bool Spawn2::Process() {
}
}
// zone state restore
if (m_stored_location != glm::vec4(0, 0, -1000, 0)) {
loc = m_stored_location;
m_stored_location = glm::vec4(0, 0, -1000, 0);
}
NPC *npc = new NPC(tmp, this, loc, GravityBehavior::Water);
NPC *npc = new NPC(tmp, this, glm::vec4(x, y, z, heading), GravityBehavior::Water);
npcthis = npc;
-4
View File
@@ -78,8 +78,6 @@ public:
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; }
inline void SetStoredLocation(const glm::vec4& loc) { m_stored_location = loc; }
protected:
friend class Zone;
@@ -107,9 +105,7 @@ 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 = {};
glm::vec4 m_stored_location = {0, 0, -1000, 0}; // use -1000 to indicate unset/zero-state
};
class SpawnCondition {
+14 -27
View File
@@ -7465,57 +7465,44 @@ void Mob::DispelMagic(Mob* caster, uint16 spell_id, int effect_value)
}
}
bool Mob::TrySpellEffectResist(uint16 spell_id)
uint16 Mob::GetSpellEffectResistChance(uint16 spell_id)
{
/*
SEResist variable
0 = spell effect id to be check if can resist
1 = percent chance of resistance
*/
if(!IsValidSpell(spell_id))
return 0;
if (!IsValidSpell(spell_id)) {
return false;
}
if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1])
return 0;
if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1]) {
return false;
}
int resist_chance = 0;
uint16 resist_chance = 0;
for(int i = 0; i < EFFECT_COUNT; ++i)
{
if (spells[spell_id].effect_id[i] == SE_Blank) {
continue;
}
bool found = false;
for(int d = 0; d < MAX_RESISTABLE_EFFECTS*2; d+=2)
{
resist_chance = 0;
if (spells[spell_id].effect_id[i] == aabonuses.SEResist[d]){
resist_chance += aabonuses.SEResist[d+1];
found = true;
}
if (spells[spell_id].effect_id[i] == itembonuses.SEResist[d]){
resist_chance += itembonuses.SEResist[d+1];
found = true;
}
if (spells[spell_id].effect_id[i] == spellbonuses.SEResist[d]){
resist_chance += spellbonuses.SEResist[d+1];
found = true;
}
if (resist_chance) {
if (zone->random.Roll(resist_chance)) {
LogSpells("Resisted spell from Spell Effect Resistance, had [{}] chance to resist spell effect id [{}]", resist_chance, spells[spell_id].effect_id[i]);
return true;
}
break;
}
if (found)
continue;
}
}
return false;
return resist_chance;
}
bool Mob::TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier){
+16 -11
View File
@@ -5359,17 +5359,16 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
if (!CharmTick) {
//Check for Spell Effect specific resistance chances (ie AA Mental Fortitude)
if (resist_type != RESIST_NONE && TrySpellEffectResist(spell_id)) {
int se_resist_bonuses = GetSpellEffectResistChance(spell_id);
if (se_resist_bonuses && zone->random.Roll(se_resist_bonuses)) {
return 0;
}
// Check for Chance to Resist Spell bonuses (ie Sanctification Discipline)
if (!spells[spell_id].no_resist && resist_type != RESIST_NONE) {
int resist_bonuses = CalcResistChanceBonus();
if (resist_bonuses && zone->random.Roll(resist_bonuses) && !IsResurrectionSicknessSpell(spell_id)) {
LogSpells("Resisted spell in sanctification, had [{}] chance to resist", resist_bonuses);
return 0;
}
int resist_bonuses = CalcResistChanceBonus();
if (resist_bonuses && zone->random.Roll(resist_bonuses) && !IsResurrectionSicknessSpell(spell_id)) {
LogSpells("Resisted spell in sanctification, had [{}] chance to resist", resist_bonuses);
return 0;
}
}
@@ -5668,12 +5667,18 @@ int16 Mob::CalcResistChanceBonus()
int16 Mob::CalcFearResistChance()
{
int resist_chance = spellbonuses.ResistFearChance + itembonuses.ResistFearChance + aabonuses.ResistFearChance;
if (spellbonuses.Fearless || itembonuses.Fearless || aabonuses.Fearless) {
resist_chance = 100;
int resistchance = spellbonuses.ResistFearChance + itembonuses.ResistFearChance;
if (IsOfClientBot()) {
resistchance += aabonuses.ResistFearChance;
if (aabonuses.Fearless == true) {
resistchance = 100;
}
}
if (spellbonuses.Fearless == true || itembonuses.Fearless == true) {
resistchance = 100;
}
return resist_chance;
return resistchance;
}
float Mob::GetAOERange(uint16 spell_id)

Some files were not shown because too many files have changed in this diff Show More