mirror of
https://github.com/EQEmu/Server.git
synced 2026-06-13 02:38:45 +00:00
Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2b2a6a5cf | |||
| 43a5bff84a | |||
| e983d07228 | |||
| 90db12483a | |||
| ff71cfbd5b | |||
| 08bb9de437 | |||
| 16e341906d | |||
| 216b3a039f | |||
| b813cf71bb | |||
| 48ecd1222f | |||
| e758b407e9 | |||
| 758dd1875e | |||
| 8e2961dda5 | |||
| 5522eda6e4 | |||
| ac1469bac2 | |||
| c2989e019a | |||
| e16b481ba2 | |||
| 4fc0ffd173 | |||
| b883888a19 | |||
| 50ad97aa0b | |||
| dca892e258 | |||
| f9fe4ea2ec | |||
| cc30c72538 | |||
| d1fd40cd85 | |||
| f3af458cb3 | |||
| a093d04594 | |||
| 115df81400 | |||
| 5babc864b9 | |||
| 60a2dd8616 | |||
| 9be2485330 | |||
| a2bf10624a | |||
| ed58d16f1f | |||
| e9b45fb360 | |||
| 803972873a | |||
| d9e57eca79 | |||
| 6429dc80d3 | |||
| c4262b3fa6 | |||
| 92128b98fd | |||
| b9cfdea76c | |||
| c8a7066d0e | |||
| 950cc4a325 | |||
| 82b48fe6e8 | |||
| ca9c1fdd24 | |||
| fe08961d25 | |||
| 5b9f7ff4c9 | |||
| 23743a4050 | |||
| 444d688ad2 | |||
| d554eb3423 | |||
| deb298dda7 | |||
| 19e785b842 | |||
| 7b9691d486 | |||
| 30fddcc5a0 | |||
| 235e59a2d8 | |||
| bc1ffe0716 | |||
| 44497414db | |||
| 96e34fe8f7 | |||
| ceca28d2a3 | |||
| b040571427 | |||
| 49664cc1a0 | |||
| 5e4fd43920 | |||
| 937b947597 | |||
| bb70850421 | |||
| 46511365a7 | |||
| 6d69ac7a98 | |||
| 938937c271 | |||
| cd808416c8 | |||
| a05d0752f6 | |||
| 799609fb21 | |||
| 213fe6a9e9 | |||
| be6a5d5f50 | |||
| 1af29bd7b1 | |||
| ef945e6e99 | |||
| 9528c1e7fc | |||
| fd6e5f465d | |||
| d00125abe1 | |||
| 5d69235a4c | |||
| e93785f885 | |||
| 3c2545cfaf | |||
| 8d1a9efac9 | |||
| f6b18fb003 | |||
| 00e77f190c | |||
| 9cb72a6ba7 | |||
| 8203c034bf |
+177
-1
@@ -1,4 +1,180 @@
|
|||||||
## [23.3.1] 3/11/2025
|
## [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
|
||||||
|
|
||||||
|
* Add check for simultaneous direct vendor and parcel Trader/Buyer Purchase ([#4778](https://github.com/EQEmu/Server/pull/4778)) @neckkola 2025-03-14
|
||||||
|
* Fix for rare circumstance where NPC's would have 0 health on restore @Akkadius
|
||||||
|
|
||||||
|
## [23.3.3] 3/13/2025
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
* Add indexes for data_buckets and zone_state_spawns ([#4771](https://github.com/EQEmu/Server/pull/4771)) @Akkadius 2025-03-11
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* Update GuildBank to correctly handle items with charges equal to zero ([#4774](https://github.com/EQEmu/Server/pull/4774)) @neckkola 2025-03-13
|
||||||
|
|
||||||
|
### Networking
|
||||||
|
|
||||||
|
* Fix "port in use" error ([#4772](https://github.com/EQEmu/Server/pull/4772)) @Akkadius 2025-03-12
|
||||||
|
|
||||||
|
### Zone
|
||||||
|
|
||||||
|
* Zone State Improvements Part 3 ([#4773](https://github.com/EQEmu/Server/pull/4773)) @Akkadius 2025-03-13
|
||||||
|
|
||||||
|
## [23.3.2] 3/11/2025
|
||||||
|
|
||||||
### DynamicZones
|
### DynamicZones
|
||||||
|
|
||||||
|
|||||||
@@ -671,6 +671,7 @@ SET(common_headers
|
|||||||
net/console_server_connection.h
|
net/console_server_connection.h
|
||||||
net/crc32.h
|
net/crc32.h
|
||||||
net/daybreak_connection.h
|
net/daybreak_connection.h
|
||||||
|
net/daybreak_pooling.h
|
||||||
net/daybreak_structs.h
|
net/daybreak_structs.h
|
||||||
net/dns.h
|
net/dns.h
|
||||||
net/endian.h
|
net/endian.h
|
||||||
@@ -682,6 +683,7 @@ SET(common_headers
|
|||||||
net/servertalk_server.h
|
net/servertalk_server.h
|
||||||
net/servertalk_server_connection.h
|
net/servertalk_server_connection.h
|
||||||
net/tcp_connection.h
|
net/tcp_connection.h
|
||||||
|
net/tcp_connection_pooling.h
|
||||||
net/tcp_server.h
|
net/tcp_server.h
|
||||||
net/websocket_server.h
|
net/websocket_server.h
|
||||||
net/websocket_server_connection.h
|
net/websocket_server_connection.h
|
||||||
@@ -742,6 +744,7 @@ SOURCE_GROUP(Net FILES
|
|||||||
net/crc32.h
|
net/crc32.h
|
||||||
net/daybreak_connection.cpp
|
net/daybreak_connection.cpp
|
||||||
net/daybreak_connection.h
|
net/daybreak_connection.h
|
||||||
|
net/daybreak_pooling.h
|
||||||
net/daybreak_structs.h
|
net/daybreak_structs.h
|
||||||
net/dns.h
|
net/dns.h
|
||||||
net/endian.h
|
net/endian.h
|
||||||
@@ -762,6 +765,7 @@ SOURCE_GROUP(Net FILES
|
|||||||
net/servertalk_server_connection.h
|
net/servertalk_server_connection.h
|
||||||
net/tcp_connection.cpp
|
net/tcp_connection.cpp
|
||||||
net/tcp_connection.h
|
net/tcp_connection.h
|
||||||
|
net/tcp_connection_pooling.h
|
||||||
net/tcp_server.cpp
|
net/tcp_server.cpp
|
||||||
net/tcp_server.h
|
net/tcp_server.h
|
||||||
net/websocket_server.cpp
|
net/websocket_server.cpp
|
||||||
|
|||||||
+2
-1
@@ -279,7 +279,8 @@ Bazaar::GetSearchResults(
|
|||||||
trader_items_ids,
|
trader_items_ids,
|
||||||
std::string(search.item_name),
|
std::string(search.item_name),
|
||||||
field_criteria_items,
|
field_criteria_items,
|
||||||
where_criteria_items
|
where_criteria_items,
|
||||||
|
search.max_results
|
||||||
);
|
);
|
||||||
|
|
||||||
if (item_results.empty()) {
|
if (item_results.empty()) {
|
||||||
|
|||||||
@@ -955,6 +955,29 @@ bool Database::UpdateName(const std::string& old_name, const std::string& new_na
|
|||||||
return CharacterDataRepository::UpdateOne(*this, e);
|
return CharacterDataRepository::UpdateOne(*this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Database::UpdateNameByID(const int character_id, const std::string& new_name)
|
||||||
|
{
|
||||||
|
LogInfo("Renaming [{}] to [{}]", character_id, new_name);
|
||||||
|
|
||||||
|
auto l = CharacterDataRepository::GetWhere(
|
||||||
|
*this,
|
||||||
|
fmt::format(
|
||||||
|
"`id` = {}",
|
||||||
|
character_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (l.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& e = l.front();
|
||||||
|
|
||||||
|
e.name = new_name;
|
||||||
|
|
||||||
|
return CharacterDataRepository::UpdateOne(*this, e);
|
||||||
|
}
|
||||||
|
|
||||||
bool Database::IsNameUsed(const std::string& name)
|
bool Database::IsNameUsed(const std::string& name)
|
||||||
{
|
{
|
||||||
if (RuleB(Bots, Enabled)) {
|
if (RuleB(Bots, Enabled)) {
|
||||||
@@ -982,6 +1005,20 @@ bool Database::IsNameUsed(const std::string& name)
|
|||||||
return !character_data.empty();
|
return !character_data.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Players cannot have the same name as a pet vanity name, or memory corruption occurs.
|
||||||
|
bool Database::IsPetNameUsed(const std::string& name)
|
||||||
|
{
|
||||||
|
const auto& pet_name_data = CharacterPetNameRepository::GetWhere(
|
||||||
|
*this,
|
||||||
|
fmt::format(
|
||||||
|
"`name` = '{}'",
|
||||||
|
Strings::Escape(name)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return !pet_name_data.empty();
|
||||||
|
}
|
||||||
|
|
||||||
uint32 Database::GetServerType()
|
uint32 Database::GetServerType()
|
||||||
{
|
{
|
||||||
const auto& l = VariablesRepository::GetWhere(*this, "`varname` = 'ServerType' LIMIT 1");
|
const auto& l = VariablesRepository::GetWhere(*this, "`varname` = 'ServerType' LIMIT 1");
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ public:
|
|||||||
bool ReserveName(uint32 account_id, const std::string& name);
|
bool ReserveName(uint32 account_id, const std::string& name);
|
||||||
bool SaveCharacterCreate(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp);
|
bool SaveCharacterCreate(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp);
|
||||||
bool UpdateName(const std::string& old_name, const std::string& new_name);
|
bool UpdateName(const std::string& old_name, const std::string& new_name);
|
||||||
|
bool UpdateNameByID(const int character_id, const std::string& new_name);
|
||||||
bool CopyCharacter(
|
bool CopyCharacter(
|
||||||
const std::string& source_character_name,
|
const std::string& source_character_name,
|
||||||
const std::string& destination_character_name,
|
const std::string& destination_character_name,
|
||||||
@@ -116,6 +117,7 @@ public:
|
|||||||
bool CheckGMIPs(const std::string& login_ip, uint32 account_id);
|
bool CheckGMIPs(const std::string& login_ip, uint32 account_id);
|
||||||
bool CheckNameFilter(const std::string& name, bool surname = false);
|
bool CheckNameFilter(const std::string& name, bool surname = false);
|
||||||
bool IsNameUsed(const std::string& name);
|
bool IsNameUsed(const std::string& name);
|
||||||
|
bool IsPetNameUsed(const std::string& name);
|
||||||
|
|
||||||
uint32 GetAccountIDByChar(const std::string& name, uint32* character_id = 0);
|
uint32 GetAccountIDByChar(const std::string& name, uint32* character_id = 0);
|
||||||
uint32 GetAccountIDByChar(uint32 character_id);
|
uint32 GetAccountIDByChar(uint32 character_id);
|
||||||
@@ -139,6 +141,7 @@ public:
|
|||||||
bool CheckInstanceExpired(uint16 instance_id);
|
bool CheckInstanceExpired(uint16 instance_id);
|
||||||
bool CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version, uint32 duration);
|
bool CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version, uint32 duration);
|
||||||
bool GetUnusedInstanceID(uint16& instance_id);
|
bool GetUnusedInstanceID(uint16& instance_id);
|
||||||
|
bool TryGetUnusedInstanceID(uint16& instance_id);
|
||||||
bool IsGlobalInstance(uint16 instance_id);
|
bool IsGlobalInstance(uint16 instance_id);
|
||||||
bool RemoveClientFromInstance(uint16 instance_id, uint32 char_id);
|
bool RemoveClientFromInstance(uint16 instance_id, uint32 char_id);
|
||||||
bool RemoveClientsFromInstance(uint16 instance_id);
|
bool RemoveClientsFromInstance(uint16 instance_id);
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ private:
|
|||||||
bool dump_system_tables = false;
|
bool dump_system_tables = false;
|
||||||
bool dump_content_tables = false;
|
bool dump_content_tables = false;
|
||||||
bool dump_player_tables = false;
|
bool dump_player_tables = false;
|
||||||
bool dump_query_server_tables = false;
|
|
||||||
bool dump_login_server_tables = false;
|
bool dump_login_server_tables = false;
|
||||||
bool dump_with_no_data = false;
|
bool dump_with_no_data = false;
|
||||||
bool dump_table_lock = false;
|
bool dump_table_lock = false;
|
||||||
|
|||||||
@@ -6792,7 +6792,7 @@ UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 341) + 5810)
|
|||||||
},
|
},
|
||||||
ManifestEntry{
|
ManifestEntry{
|
||||||
.version = 9304,
|
.version = 9304,
|
||||||
.description = "2024_12_01_2024_update_guild_bank",
|
.description = "2024_12_01_update_guild_bank",
|
||||||
.check = "SHOW COLUMNS FROM `guild_bank` LIKE 'augment_one_id'",
|
.check = "SHOW COLUMNS FROM `guild_bank` LIKE 'augment_one_id'",
|
||||||
.condition = "empty",
|
.condition = "empty",
|
||||||
.match = "",
|
.match = "",
|
||||||
@@ -6845,7 +6845,7 @@ RENAME TABLE `expedition_lockouts` TO `dynamic_zone_lockouts`;
|
|||||||
.condition = "empty",
|
.condition = "empty",
|
||||||
.match = "",
|
.match = "",
|
||||||
.sql = R"(
|
.sql = R"(
|
||||||
-- ✅ Drop old indexes
|
-- Drop old indexes if exists
|
||||||
DROP INDEX IF EXISTS `keys` ON `data_buckets`;
|
DROP INDEX IF EXISTS `keys` ON `data_buckets`;
|
||||||
DROP INDEX IF EXISTS `idx_npc_expires` ON `data_buckets`;
|
DROP INDEX IF EXISTS `idx_npc_expires` ON `data_buckets`;
|
||||||
DROP INDEX IF EXISTS `idx_bot_expires` ON `data_buckets`;
|
DROP INDEX IF EXISTS `idx_bot_expires` ON `data_buckets`;
|
||||||
@@ -6863,10 +6863,10 @@ ALTER TABLE `data_buckets`
|
|||||||
MODIFY COLUMN `npc_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `character_id`,
|
MODIFY COLUMN `npc_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `character_id`,
|
||||||
MODIFY COLUMN `bot_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `npc_id`;
|
MODIFY COLUMN `bot_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `npc_id`;
|
||||||
|
|
||||||
-- ✅ Create optimized unique index with `key` first
|
-- Create optimized unique index with `key` first
|
||||||
CREATE UNIQUE INDEX `keys` ON data_buckets (`key`, character_id, npc_id, bot_id, account_id, zone_id, instance_id);
|
CREATE UNIQUE INDEX `keys` ON data_buckets (`key`, character_id, npc_id, bot_id, account_id, zone_id, instance_id);
|
||||||
|
|
||||||
-- ✅ Create indexes for just instance_id (instance deletion)
|
-- Create indexes for just instance_id (instance deletion)
|
||||||
CREATE INDEX idx_instance_id ON data_buckets (instance_id);
|
CREATE INDEX idx_instance_id ON data_buckets (instance_id);
|
||||||
)",
|
)",
|
||||||
.content_schema_update = false
|
.content_schema_update = false
|
||||||
@@ -6914,7 +6914,7 @@ CREATE TABLE `zone_state_spawns` (
|
|||||||
},
|
},
|
||||||
ManifestEntry{
|
ManifestEntry{
|
||||||
.version = 9308,
|
.version = 9308,
|
||||||
.description = "2025_add_multivalue_support_to_evolving_subtype.sql",
|
.description = "2025_03_29_add_multivalue_support_to_evolving_subtype.sql",
|
||||||
.check = "SHOW COLUMNS FROM `items_evolving_details` LIKE 'sub_type'",
|
.check = "SHOW COLUMNS FROM `items_evolving_details` LIKE 'sub_type'",
|
||||||
.condition = "missing",
|
.condition = "missing",
|
||||||
.match = "varchar(200)",
|
.match = "varchar(200)",
|
||||||
@@ -6942,8 +6942,8 @@ CREATE TABLE `character_pet_name` (
|
|||||||
.version = 9310,
|
.version = 9310,
|
||||||
.description = "2025_03_7_expand_horse_def.sql",
|
.description = "2025_03_7_expand_horse_def.sql",
|
||||||
.check = "SHOW COLUMNS FROM `horses` LIKE 'helmtexture'",
|
.check = "SHOW COLUMNS FROM `horses` LIKE 'helmtexture'",
|
||||||
.condition = "missing",
|
.condition = "empty",
|
||||||
.match = "TINYINT(2)",
|
.match = "",
|
||||||
.sql = R"(
|
.sql = R"(
|
||||||
ALTER TABLE `horses`
|
ALTER TABLE `horses`
|
||||||
ADD COLUMN `helmtexture` TINYINT(2) NOT NULL DEFAULT -1 AFTER `texture`;
|
ADD COLUMN `helmtexture` TINYINT(2) NOT NULL DEFAULT -1 AFTER `texture`;
|
||||||
@@ -6959,6 +6959,131 @@ ALTER TABLE `horses`
|
|||||||
.sql = R"(
|
.sql = R"(
|
||||||
ALTER TABLE `zone_state_spawns`
|
ALTER TABLE `zone_state_spawns`
|
||||||
ADD COLUMN `is_zone` tinyint(11) NULL DEFAULT 0 AFTER `is_corpse`;
|
ADD COLUMN `is_zone` tinyint(11) NULL DEFAULT 0 AFTER `is_corpse`;
|
||||||
|
)",
|
||||||
|
.content_schema_update = false
|
||||||
|
},
|
||||||
|
ManifestEntry{
|
||||||
|
.version = 9312,
|
||||||
|
.description = "2025_03_11_data_bucket_indexes.sql",
|
||||||
|
.check = "SHOW INDEX FROM data_buckets",
|
||||||
|
.condition = "missing",
|
||||||
|
.match = "idx_zone_instance_expires",
|
||||||
|
.sql = R"(
|
||||||
|
DROP INDEX IF EXISTS `idx_zone_instance_expires` ON `data_buckets`;
|
||||||
|
DROP INDEX IF EXISTS `idx_character_expires` ON `data_buckets`;
|
||||||
|
DROP INDEX IF EXISTS `idx_bot_expires` ON `data_buckets`;
|
||||||
|
ALTER TABLE data_buckets ADD INDEX idx_zone_instance_expires (zone_id, instance_id, expires);
|
||||||
|
ALTER TABLE data_buckets ADD INDEX idx_character_expires (character_id, expires);
|
||||||
|
ALTER TABLE data_buckets ADD INDEX idx_bot_expires (bot_id, expires);
|
||||||
|
)",
|
||||||
|
.content_schema_update = false
|
||||||
|
},
|
||||||
|
ManifestEntry{
|
||||||
|
.version = 9313,
|
||||||
|
.description = "2025_03_11_zone_state_spawns.sql",
|
||||||
|
.check = "SHOW INDEX FROM zone_state_spawns",
|
||||||
|
.condition = "missing",
|
||||||
|
.match = "idx_zone_instance",
|
||||||
|
.sql = R"(
|
||||||
|
ALTER TABLE zone_state_spawns ADD INDEX idx_zone_instance (zone_id, instance_id);
|
||||||
|
)",
|
||||||
|
.content_schema_update = false
|
||||||
|
},
|
||||||
|
ManifestEntry{
|
||||||
|
.version = 9314,
|
||||||
|
.description = "2025_03_12_zone_state_spawns_one_time_truncate.sql",
|
||||||
|
.check = "SELECT * FROM db_version WHERE version >= 9314",
|
||||||
|
.condition = "empty",
|
||||||
|
.match = "",
|
||||||
|
.sql = R"(
|
||||||
|
TRUNCATE TABLE zone_state_spawns;
|
||||||
|
)",
|
||||||
|
.content_schema_update = false
|
||||||
|
},
|
||||||
|
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
|
.content_schema_update = false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -128,11 +128,35 @@ bool Database::CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version
|
|||||||
e.version = version;
|
e.version = version;
|
||||||
e.start_time = std::time(nullptr);
|
e.start_time = std::time(nullptr);
|
||||||
e.duration = duration;
|
e.duration = duration;
|
||||||
|
e.expire_at = e.start_time + duration;
|
||||||
|
|
||||||
return InstanceListRepository::InsertOne(*this, e).id;
|
RespawnTimesRepository::ClearInstanceTimers(*this, e.id);
|
||||||
|
InstanceListRepository::ReplaceOne(*this, e);
|
||||||
|
return instance_id > 0 && e.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Database::GetUnusedInstanceID(uint16 &instance_id)
|
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_reserved_instance_id = RuleI(Instances, ReservedInstances);
|
||||||
uint32 max_instance_id = 32000;
|
uint32 max_instance_id = 32000;
|
||||||
@@ -537,14 +561,12 @@ void Database::GetCharactersInInstance(uint16 instance_id, std::list<uint32> &ch
|
|||||||
|
|
||||||
void Database::PurgeExpiredInstances()
|
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(
|
auto l = InstanceListRepository::GetWhere(
|
||||||
*this,
|
*this,
|
||||||
"(start_time + duration) <= (UNIX_TIMESTAMP() - 86400) AND never_expires = 0"
|
fmt::format(
|
||||||
|
"expire_at <= (UNIX_TIMESTAMP() - {}) and expire_at != 0 AND never_expires = 0",
|
||||||
|
RuleI(Instances, ExpireOffsetTimeSeconds)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
if (l.empty()) {
|
if (l.empty()) {
|
||||||
return;
|
return;
|
||||||
@@ -555,20 +577,24 @@ void Database::PurgeExpiredInstances()
|
|||||||
instance_ids.emplace_back(std::to_string(e.id));
|
instance_ids.emplace_back(std::to_string(e.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto imploded_instance_ids = Strings::Implode(",", instance_ids);
|
const auto ids = Strings::Implode(",", instance_ids);
|
||||||
|
|
||||||
InstanceListRepository::DeleteWhere(*this, fmt::format("id IN ({})", imploded_instance_ids));
|
TransactionBegin();
|
||||||
InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id IN ({})", imploded_instance_ids));
|
InstanceListRepository::DeleteWhere(*this, fmt::format("id IN ({})", ids));
|
||||||
RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id IN ({})", ids));
|
||||||
SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
|
||||||
CharacterCorpsesRepository::BuryInstances(*this, imploded_instance_ids);
|
SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
|
||||||
DynamicZoneMembersRepository::DeleteByManyInstances(*this, imploded_instance_ids);
|
CharacterCorpsesRepository::BuryInstances(*this, ids);
|
||||||
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
DynamicZoneMembersRepository::DeleteByManyInstances(*this, ids);
|
||||||
Spawn2DisabledRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
|
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
|
||||||
DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id != 0 and instance_id IN ({})", imploded_instance_ids));
|
Spawn2DisabledRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
|
||||||
|
DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id != 0 and instance_id IN ({})", ids));
|
||||||
if (RuleB(Zone, StateSavingOnShutdown)) {
|
if (RuleB(Zone, StateSavingOnShutdown)) {
|
||||||
ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` IN ({})", imploded_instance_ids));
|
ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` IN ({})", ids));
|
||||||
}
|
}
|
||||||
|
TransactionCommit();
|
||||||
|
|
||||||
|
LogInfo("Purged [{}] expired instances", l.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
|
void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
|
||||||
@@ -580,6 +606,7 @@ void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
|
|||||||
|
|
||||||
i.start_time = std::time(nullptr);
|
i.start_time = std::time(nullptr);
|
||||||
i.duration = new_duration;
|
i.duration = new_duration;
|
||||||
|
i.expire_at = i.start_time + i.duration;
|
||||||
|
|
||||||
InstanceListRepository::UpdateOne(*this, i);
|
InstanceListRepository::UpdateOne(*this, i);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,15 +58,16 @@ uint32_t DynamicZoneBase::CreateInstance()
|
|||||||
insert_instance.start_time = static_cast<int>(std::chrono::system_clock::to_time_t(m_start_time));
|
insert_instance.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.duration = static_cast<int>(m_duration.count());
|
||||||
insert_instance.never_expires = m_never_expires;
|
insert_instance.never_expires = m_never_expires;
|
||||||
|
insert_instance.expire_at = insert_instance.start_time + insert_instance.duration;
|
||||||
|
|
||||||
auto instance = InstanceListRepository::InsertOne(GetDatabase(), insert_instance);
|
auto instance = InstanceListRepository::ReplaceOne(GetDatabase(), insert_instance);
|
||||||
if (instance.id == 0)
|
if (!instance)
|
||||||
{
|
{
|
||||||
LogDynamicZones("Failed to create instance [{}] for zone [{}]", unused_instance_id, m_zone_id);
|
LogDynamicZones("Failed to create instance [{}] for zone [{}]", unused_instance_id, m_zone_id);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_instance_id = instance.id;
|
m_instance_id = unused_instance_id;
|
||||||
|
|
||||||
return m_instance_id;
|
return m_instance_id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,6 +287,8 @@ N(OP_InstillDoubt),
|
|||||||
N(OP_InterruptCast),
|
N(OP_InterruptCast),
|
||||||
N(OP_InvokeChangePetName),
|
N(OP_InvokeChangePetName),
|
||||||
N(OP_InvokeChangePetNameImmediate),
|
N(OP_InvokeChangePetNameImmediate),
|
||||||
|
N(OP_InvokeNameChangeImmediate),
|
||||||
|
N(OP_InvokeNameChangeLazy),
|
||||||
N(OP_ItemLinkClick),
|
N(OP_ItemLinkClick),
|
||||||
N(OP_ItemLinkResponse),
|
N(OP_ItemLinkResponse),
|
||||||
N(OP_ItemLinkText),
|
N(OP_ItemLinkText),
|
||||||
|
|||||||
@@ -5832,21 +5832,28 @@ struct ChangeSize_Struct
|
|||||||
/*16*/
|
/*16*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum ChangeNameResponse : int {
|
||||||
|
Denied = 0, // 5167: "You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name."
|
||||||
|
Accepted = 1, // 5976: "Your request for a name change was successful."
|
||||||
|
Timeout = -1, // 5977: "Your request for a name change has timed out. Please try again later."
|
||||||
|
ServerError = -2, // 5978: "The server had an error while processing your name request. Please try again later."
|
||||||
|
RateLimited = -3, // 5979: "You must wait longer before submitting another name request. Please try again in a few minutes."
|
||||||
|
Ineligible = -4, // 5980: "Your character is not eligible for a name change."
|
||||||
|
Pending = -5 // 5193: "You already have a name change pending. Please wait until it is fully processed before attempting another name change."
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AltChangeName_Struct {
|
||||||
|
/*00*/ char new_name[64];
|
||||||
|
/*40*/ char old_name[64];
|
||||||
|
/*80*/ int response_code;
|
||||||
|
};
|
||||||
|
|
||||||
struct ChangePetName_Struct {
|
struct ChangePetName_Struct {
|
||||||
/*00*/ char new_pet_name[64];
|
/*00*/ char new_pet_name[64];
|
||||||
/*40*/ char pet_owner_name[64];
|
/*40*/ char pet_owner_name[64];
|
||||||
/*80*/ int response_code;
|
/*80*/ int response_code;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ChangePetNameResponse : int {
|
|
||||||
Denied = 0, // 5167 You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name.
|
|
||||||
Accepted = 1, // 5976 Your request for a name change was successful.
|
|
||||||
Timeout = -3, // 5979 You must wait longer before submitting another name request. Please try again in a few minutes.
|
|
||||||
NotEligible = -4, // 5980 Your character is not eligible for a name change.
|
|
||||||
Pending = -5, // 5193 You already have a name change pending. Please wait until it is fully processed before attempting another name change.
|
|
||||||
Unhandled = -1
|
|
||||||
};
|
|
||||||
|
|
||||||
// New OpCode/Struct for SoD+
|
// New OpCode/Struct for SoD+
|
||||||
struct GroupMakeLeader_Struct
|
struct GroupMakeLeader_Struct
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ namespace Logs {
|
|||||||
Spawns,
|
Spawns,
|
||||||
Spells,
|
Spells,
|
||||||
Status, // deprecated
|
Status, // deprecated
|
||||||
TCPConnection,
|
TCPConnection, // deprecated
|
||||||
Tasks,
|
Tasks,
|
||||||
Tradeskills,
|
Tradeskills,
|
||||||
Trading,
|
Trading,
|
||||||
@@ -150,6 +150,8 @@ namespace Logs {
|
|||||||
BotSpellTypeChecks,
|
BotSpellTypeChecks,
|
||||||
NpcHandin,
|
NpcHandin,
|
||||||
ZoneState,
|
ZoneState,
|
||||||
|
NetClient,
|
||||||
|
NetTCP,
|
||||||
MaxCategoryID /* Don't Remove this */
|
MaxCategoryID /* Don't Remove this */
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,7 +185,7 @@ namespace Logs {
|
|||||||
"Spawns",
|
"Spawns",
|
||||||
"Spells",
|
"Spells",
|
||||||
"Status (Deprecated)",
|
"Status (Deprecated)",
|
||||||
"TCP Connection",
|
"TCP Connection (Deprecated)",
|
||||||
"Tasks",
|
"Tasks",
|
||||||
"Tradeskills",
|
"Tradeskills",
|
||||||
"Trading",
|
"Trading",
|
||||||
@@ -258,7 +260,9 @@ namespace Logs {
|
|||||||
"Bot Spell Checks",
|
"Bot Spell Checks",
|
||||||
"Bot Spell Type Checks",
|
"Bot Spell Type Checks",
|
||||||
"NpcHandin",
|
"NpcHandin",
|
||||||
"ZoneState"
|
"ZoneState",
|
||||||
|
"Net Server <-> Client",
|
||||||
|
"Net TCP"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -261,26 +261,6 @@
|
|||||||
OutF(LogSys, Logs::Detail, Logs::Spells, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
OutF(LogSys, Logs::Detail, Logs::Spells, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||||
} while (0)
|
} 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 {\
|
#define LogTasks(message, ...) do {\
|
||||||
if (LogSys.IsLogEnabled(Logs::General, Logs::Tasks))\
|
if (LogSys.IsLogEnabled(Logs::General, Logs::Tasks))\
|
||||||
OutF(LogSys, Logs::General, Logs::Tasks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
OutF(LogSys, Logs::General, Logs::Tasks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||||
@@ -924,6 +904,26 @@
|
|||||||
OutF(LogSys, Logs::Detail, Logs::ZoneState, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
OutF(LogSys, Logs::Detail, Logs::ZoneState, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||||
} while (0)
|
} 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 {\
|
#define Log(debug_level, log_category, message, ...) do {\
|
||||||
if (LogSys.IsLogEnabled(debug_level, log_category))\
|
if (LogSys.IsLogEnabled(debug_level, log_category))\
|
||||||
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||||
|
|||||||
@@ -181,9 +181,17 @@ void PlayerEventLogs::ProcessBatchQueue()
|
|||||||
|
|
||||||
// Helper to deserialize event data
|
// Helper to deserialize event data
|
||||||
auto Deserialize = [](const std::string &data, auto &out) {
|
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);
|
std::stringstream ss(data);
|
||||||
cereal::JSONInputArchive ar(ss);
|
cereal::JSONInputArchive ar(ss);
|
||||||
out.serialize(ar);
|
out.serialize(ar);
|
||||||
|
}
|
||||||
|
catch (const std::exception &e) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper to assign ETL table ID
|
// Helper to assign ETL table ID
|
||||||
|
|||||||
@@ -303,6 +303,14 @@ bool IpUtil::IsPortInUse(const std::string& ip, int port) {
|
|||||||
return true; // Assume in use on failure
|
return true; // Assume in use on failure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
int opt = 1;
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&opt, sizeof(opt)); // Windows-specific
|
||||||
|
#else
|
||||||
|
int opt = 1;
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // Linux/macOS
|
||||||
|
#endif
|
||||||
|
|
||||||
sockaddr_in addr{};
|
sockaddr_in addr{};
|
||||||
addr.sin_family = AF_INET;
|
addr.sin_family = AF_INET;
|
||||||
addr.sin_port = htons(port);
|
addr.sin_port = htons(port);
|
||||||
|
|||||||
@@ -906,26 +906,34 @@ bool EQ::ItemInstance::IsSlotAllowed(int16 slot_id) const {
|
|||||||
|
|
||||||
bool EQ::ItemInstance::IsDroppable(bool recurse) const
|
bool EQ::ItemInstance::IsDroppable(bool recurse) const
|
||||||
{
|
{
|
||||||
if (!m_item)
|
if (!m_item) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
/*if (m_ornamentidfile) // not implemented
|
/*if (m_ornamentidfile) // not implemented
|
||||||
return false;*/
|
return false;*/
|
||||||
if (m_attuned)
|
if (m_attuned) {
|
||||||
return false;
|
return false;
|
||||||
/*if (m_item->FVNoDrop != 0) // not implemented
|
}
|
||||||
return false;*/
|
|
||||||
if (m_item->NoDrop == 0)
|
if (RuleI(World, FVNoDropFlag) == FVNoDropFlagRule::Enabled && m_item->FVNoDrop == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_item->NoDrop == 0) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (recurse) {
|
if (recurse) {
|
||||||
for (auto iter: m_contents) {
|
for (auto iter: m_contents) {
|
||||||
if (!iter.second)
|
if (!iter.second) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!iter.second->IsDroppable(recurse))
|
if (!iter.second->IsDroppable(recurse)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
#include "daybreak_connection.h"
|
#include "daybreak_connection.h"
|
||||||
#include "../event/event_loop.h"
|
#include "../event/event_loop.h"
|
||||||
#include "../event/task.h"
|
|
||||||
#include "../data_verification.h"
|
#include "../data_verification.h"
|
||||||
#include "crc32.h"
|
#include "crc32.h"
|
||||||
#include "../eqemu_logsys.h"
|
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <sstream>
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
EQ::Net::DaybreakConnectionManager::DaybreakConnectionManager()
|
EQ::Net::DaybreakConnectionManager::DaybreakConnectionManager()
|
||||||
{
|
{
|
||||||
@@ -53,16 +57,22 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop)
|
|||||||
uv_ip4_addr("0.0.0.0", m_options.port, &recv_addr);
|
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);
|
int rc = uv_udp_bind(&m_socket, (const struct sockaddr *)&recv_addr, UV_UDP_REUSEADDR);
|
||||||
|
|
||||||
rc = uv_udp_recv_start(&m_socket,
|
rc = uv_udp_recv_start(
|
||||||
|
&m_socket,
|
||||||
[](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
|
[](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
|
||||||
|
if (suggested_size > 65536) {
|
||||||
buf->base = new char[suggested_size];
|
buf->base = new char[suggested_size];
|
||||||
memset(buf->base, 0, suggested_size);
|
|
||||||
buf->len = suggested_size;
|
buf->len = suggested_size;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static thread_local char temp_buf[65536];
|
||||||
|
buf->base = temp_buf;
|
||||||
|
buf->len = 65536;
|
||||||
},
|
},
|
||||||
[](uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) {
|
[](uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) {
|
||||||
DaybreakConnectionManager *c = (DaybreakConnectionManager*)handle->data;
|
DaybreakConnectionManager *c = (DaybreakConnectionManager*)handle->data;
|
||||||
if (nread < 0 || addr == nullptr) {
|
if (nread < 0 || addr == nullptr) {
|
||||||
delete[] buf->base;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +80,10 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop)
|
|||||||
uv_ip4_name((const sockaddr_in*)addr, endpoint, 16);
|
uv_ip4_name((const sockaddr_in*)addr, endpoint, 16);
|
||||||
auto port = ntohs(((const sockaddr_in*)addr)->sin_port);
|
auto port = ntohs(((const sockaddr_in*)addr)->sin_port);
|
||||||
c->ProcessPacket(endpoint, port, buf->base, nread);
|
c->ProcessPacket(endpoint, port, buf->base, nread);
|
||||||
|
|
||||||
|
if (buf->len > 65536) {
|
||||||
delete[] buf->base;
|
delete[] buf->base;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
m_attached = loop;
|
m_attached = loop;
|
||||||
@@ -310,7 +323,7 @@ EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner
|
|||||||
m_last_session_stats = Clock::now();
|
m_last_session_stats = Clock::now();
|
||||||
m_outgoing_budget = owner->m_options.outgoing_data_rate;
|
m_outgoing_budget = owner->m_options.outgoing_data_rate;
|
||||||
|
|
||||||
LogNetcode("New session [{}] with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
|
LogNetClient("New session [{}] with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
|
||||||
}
|
}
|
||||||
|
|
||||||
//new connection made as client
|
//new connection made as client
|
||||||
@@ -342,17 +355,17 @@ EQ::Net::DaybreakConnection::~DaybreakConnection()
|
|||||||
|
|
||||||
void EQ::Net::DaybreakConnection::Close()
|
void EQ::Net::DaybreakConnection::Close()
|
||||||
{
|
{
|
||||||
if (m_status == StatusConnected) {
|
if (m_status != StatusDisconnected && m_status != StatusDisconnecting) {
|
||||||
FlushBuffer();
|
FlushBuffer();
|
||||||
SendDisconnect();
|
SendDisconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_status != StatusDisconnecting) {
|
||||||
m_close_time = Clock::now();
|
m_close_time = Clock::now();
|
||||||
ChangeStatus(StatusDisconnecting);
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
ChangeStatus(StatusDisconnecting);
|
ChangeStatus(StatusDisconnecting);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void EQ::Net::DaybreakConnection::QueuePacket(Packet &p)
|
void EQ::Net::DaybreakConnection::QueuePacket(Packet &p)
|
||||||
{
|
{
|
||||||
@@ -634,7 +647,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
|
|||||||
p.PutSerialize(0, reply);
|
p.PutSerialize(0, reply);
|
||||||
InternalSend(p);
|
InternalSend(p);
|
||||||
|
|
||||||
LogNetcode("[OP_SessionRequest] Session [{}] started with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
|
LogNetClient("[OP_SessionRequest] Session [{}] started with encode key [{}]", m_connect_code, HostToNetwork(m_encode_key));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -653,7 +666,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
|
|||||||
m_max_packet_size = reply.max_packet_size;
|
m_max_packet_size = reply.max_packet_size;
|
||||||
ChangeStatus(StatusConnected);
|
ChangeStatus(StatusConnected);
|
||||||
|
|
||||||
LogNetcode(
|
LogNetClient(
|
||||||
"[OP_SessionResponse] Session [{}] refresh with encode key [{}]",
|
"[OP_SessionResponse] Session [{}] refresh with encode key [{}]",
|
||||||
m_connect_code,
|
m_connect_code,
|
||||||
HostToNetwork(m_encode_key)
|
HostToNetwork(m_encode_key)
|
||||||
@@ -782,7 +795,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
|
|||||||
SendDisconnect();
|
SendDisconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
LogNetcode(
|
LogNetClient(
|
||||||
"[OP_SessionDisconnect] Session [{}] disconnect with encode key [{}]",
|
"[OP_SessionDisconnect] Session [{}] disconnect with encode key [{}]",
|
||||||
m_connect_code,
|
m_connect_code,
|
||||||
HostToNetwork(m_encode_key)
|
HostToNetwork(m_encode_key)
|
||||||
@@ -852,7 +865,7 @@ bool EQ::Net::DaybreakConnection::ValidateCRC(Packet &p)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (p.Length() < (size_t)m_crc_bytes) {
|
if (p.Length() < (size_t)m_crc_bytes) {
|
||||||
LogNetcode("Session [{}] ignored packet (crc bytes invalid on session)", m_connect_code);
|
LogNetClient("Session [{}] ignored packet (crc bytes invalid on session)", m_connect_code);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1043,7 +1056,7 @@ void EQ::Net::DaybreakConnection::Decompress(Packet &p, size_t offset, size_t le
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t new_buffer[4096];
|
static thread_local uint8_t new_buffer[4096];
|
||||||
uint8_t *buffer = (uint8_t*)p.Data() + offset;
|
uint8_t *buffer = (uint8_t*)p.Data() + offset;
|
||||||
uint32_t new_length = 0;
|
uint32_t new_length = 0;
|
||||||
|
|
||||||
@@ -1064,7 +1077,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)
|
void EQ::Net::DaybreakConnection::Compress(Packet &p, size_t offset, size_t length)
|
||||||
{
|
{
|
||||||
uint8_t new_buffer[2048] = { 0 };
|
static thread_local uint8_t new_buffer[2048] = { 0 };
|
||||||
uint8_t *buffer = (uint8_t*)p.Data() + offset;
|
uint8_t *buffer = (uint8_t*)p.Data() + offset;
|
||||||
uint32_t new_length = 0;
|
uint32_t new_length = 0;
|
||||||
bool send_uncompressed = true;
|
bool send_uncompressed = true;
|
||||||
@@ -1091,10 +1104,6 @@ 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)
|
void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
||||||
{
|
{
|
||||||
if (m_status == DbProtocolStatus::StatusDisconnected) {
|
if (m_status == DbProtocolStatus::StatusDisconnected) {
|
||||||
@@ -1117,23 +1126,24 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
|||||||
auto &first_packet = s->sent_packets.begin()->second;
|
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();
|
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
|
// 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 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) {
|
if (time_since_first_sent <= first_packet.resend_delay && !m_acked_since_last_resend) {
|
||||||
LogNetcodeDetail(
|
LogNetClient(
|
||||||
"Not resending packets for stream [{}] time since first sent [{}] resend delay [{}] m_acked_since_last_resend [{}]",
|
"Not resending packets for stream [{}] packets [{}] time_first_sent [{}] resend_delay [{}] m_acked_since_last_resend [{}]",
|
||||||
stream,
|
stream,
|
||||||
|
s->sent_packets.size(),
|
||||||
time_since_first_sent,
|
time_since_first_sent,
|
||||||
first_packet.resend_delay,
|
first_packet.resend_delay,
|
||||||
m_acked_since_last_resend
|
m_acked_since_last_resend
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (time_since_first_sent >= m_owner->m_options.resend_timeout) {
|
|
||||||
Close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LogSys.IsLogEnabled(Logs::Detail, Logs::Netcode)) {
|
if (LogSys.IsLogEnabled(Logs::Detail, Logs::Netcode)) {
|
||||||
@@ -1142,7 +1152,7 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
|||||||
total_size += e.second.packet.Length();
|
total_size += e.second.packet.Length();
|
||||||
}
|
}
|
||||||
|
|
||||||
LogNetcodeDetail(
|
LogNetClient(
|
||||||
"Resending packets for stream [{}] packet count [{}] total packet size [{}] m_acked_since_last_resend [{}]",
|
"Resending packets for stream [{}] packet count [{}] total packet size [{}] m_acked_since_last_resend [{}]",
|
||||||
stream,
|
stream,
|
||||||
s->sent_packets.size(),
|
s->sent_packets.size(),
|
||||||
@@ -1154,7 +1164,7 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
|
|||||||
for (auto &e: s->sent_packets) {
|
for (auto &e: s->sent_packets) {
|
||||||
if (m_resend_packets_sent >= MAX_CLIENT_RECV_PACKETS_PER_WINDOW ||
|
if (m_resend_packets_sent >= MAX_CLIENT_RECV_PACKETS_PER_WINDOW ||
|
||||||
m_resend_bytes_sent >= MAX_CLIENT_RECV_BYTES_PER_WINDOW) {
|
m_resend_bytes_sent >= MAX_CLIENT_RECV_BYTES_PER_WINDOW) {
|
||||||
LogNetcodeDetail(
|
LogNetClient(
|
||||||
"Stopping resend because we hit thresholds m_resend_packets_sent [{}] max [{}] m_resend_bytes_sent [{}] max [{}]",
|
"Stopping resend because we hit thresholds m_resend_packets_sent [{}] max [{}] m_resend_bytes_sent [{}] max [{}]",
|
||||||
m_resend_packets_sent,
|
m_resend_packets_sent,
|
||||||
MAX_CLIENT_RECV_PACKETS_PER_WINDOW,
|
MAX_CLIENT_RECV_PACKETS_PER_WINDOW,
|
||||||
@@ -1333,46 +1343,53 @@ void EQ::Net::DaybreakConnection::SendKeepAlive()
|
|||||||
InternalSend(p);
|
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) {
|
if (m_owner->m_options.outgoing_data_rate > 0.0) {
|
||||||
auto new_budget = m_outgoing_budget - (p.Length() / 1024.0);
|
auto new_budget = m_outgoing_budget - (p.Length() / 1024.0);
|
||||||
if (new_budget <= 0.0) {
|
if (new_budget <= 0.0) {
|
||||||
m_stats.dropped_datarate_packets++;
|
m_stats.dropped_datarate_packets++;
|
||||||
return;
|
return;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
m_outgoing_budget = new_budget;
|
m_outgoing_budget = new_budget;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_last_send = Clock::now();
|
m_last_send = Clock::now();
|
||||||
|
|
||||||
auto send_func = [](uv_udp_send_t* req, int status) {
|
auto pooled_opt = send_buffer_pool.acquire();
|
||||||
delete[](char*)req->data;
|
if (!pooled_opt) {
|
||||||
delete req;
|
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];
|
||||||
|
|
||||||
if (PacketCanBeEncoded(p)) {
|
if (PacketCanBeEncoded(p)) {
|
||||||
|
|
||||||
m_stats.bytes_before_encode += p.Length();
|
m_stats.bytes_before_encode += p.Length();
|
||||||
|
|
||||||
DynamicPacket out;
|
DynamicPacket out;
|
||||||
out.PutPacket(0, p);
|
out.PutPacket(0, p);
|
||||||
|
|
||||||
for (int i = 0; i < 2; ++i) {
|
for (auto &m_encode_passe: m_encode_passes) {
|
||||||
switch (m_encode_passes[i]) {
|
switch (m_encode_passe) {
|
||||||
case EncodeCompression:
|
case EncodeCompression:
|
||||||
if (out.GetInt8(0) == 0)
|
if (out.GetInt8(0) == 0) {
|
||||||
Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
|
Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
|
||||||
else
|
} else {
|
||||||
Compress(out, 1, out.Length() - 1);
|
Compress(out, 1, out.Length() - 1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case EncodeXOR:
|
case EncodeXOR:
|
||||||
if (out.GetInt8(0) == 0)
|
if (out.GetInt8(0) == 0) {
|
||||||
Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
|
Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
|
||||||
else
|
} else {
|
||||||
Encode(out, 1, out.Length() - 1);
|
Encode(out, 1, out.Length() - 1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -1380,52 +1397,43 @@ void EQ::Net::DaybreakConnection::InternalSend(Packet &p)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AppendCRC(out);
|
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());
|
memcpy(data, out.Data(), out.Length());
|
||||||
send_buffers[0] = uv_buf_init(data, out.Length());
|
send_buffers[0] = uv_buf_init(data, out.Length());
|
||||||
send_req->data = send_buffers[0].base;
|
} else {
|
||||||
|
|
||||||
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());
|
memcpy(data, p.Data(), p.Length());
|
||||||
send_buffers[0] = uv_buf_init(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_bytes += p.Length();
|
||||||
m_stats.sent_packets++;
|
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)) {
|
if (m_owner->m_options.simulated_out_packet_loss &&
|
||||||
delete[](char*)send_req->data;
|
m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) {
|
||||||
delete send_req;
|
send_buffer_pool.release(ctx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uv_udp_send(send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr*)&send_addr, send_func);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id, bool reliable)
|
void EQ::Net::DaybreakConnection::InternalQueuePacket(Packet &p, int stream_id, bool reliable)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "../random.h"
|
#include "../random.h"
|
||||||
#include "packet.h"
|
#include "packet.h"
|
||||||
#include "daybreak_structs.h"
|
#include "daybreak_structs.h"
|
||||||
|
#include "daybreak_pooling.h"
|
||||||
#include <uv.h>
|
#include <uv.h>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -171,3 +171,4 @@ namespace EQ
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,15 +62,15 @@ void EQ::Net::ServertalkClient::Connect()
|
|||||||
m_connecting = true;
|
m_connecting = true;
|
||||||
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
|
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
|
||||||
if (connection == nullptr) {
|
if (connection == nullptr) {
|
||||||
LogF(Logs::General, Logs::TCPConnection, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
LogNetTCP("Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||||
m_connecting = false;
|
m_connecting = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogF(Logs::General, Logs::TCPConnection, "Connected to {0}:{1}", m_addr, m_port);
|
LogNetTCP("Connected to {0}:{1}", m_addr, m_port);
|
||||||
m_connection = connection;
|
m_connection = connection;
|
||||||
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
|
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
|
||||||
LogF(Logs::General, Logs::TCPConnection, "Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
LogNetTCP("Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||||
m_connection.reset();
|
m_connection.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -58,15 +58,15 @@ void EQ::Net::ServertalkLegacyClient::Connect()
|
|||||||
m_connecting = true;
|
m_connecting = true;
|
||||||
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
|
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
|
||||||
if (connection == nullptr) {
|
if (connection == nullptr) {
|
||||||
LogF(Logs::General, Logs::TCPConnection, "Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
LogNetTCP("Error connecting to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||||
m_connecting = false;
|
m_connecting = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogF(Logs::General, Logs::TCPConnection, "Connected to {0}:{1}", m_addr, m_port);
|
LogNetTCP("Connected to {0}:{1}", m_addr, m_port);
|
||||||
m_connection = connection;
|
m_connection = connection;
|
||||||
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
|
m_connection->OnDisconnect([this](EQ::Net::TCPConnection *c) {
|
||||||
LogF(Logs::General, Logs::TCPConnection, "Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
LogNetTCP("Connection lost to {0}:{1}, attempting to reconnect...", m_addr, m_port);
|
||||||
m_connection.reset();
|
m_connection.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
#include "tcp_connection.h"
|
#include "tcp_connection.h"
|
||||||
#include "../event/event_loop.h"
|
#include "../event/event_loop.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
WriteReqPool tcp_write_pool;
|
||||||
|
|
||||||
void on_close_handle(uv_handle_t* handle) {
|
void on_close_handle(uv_handle_t* handle) {
|
||||||
delete (uv_tcp_t *)handle;
|
delete (uv_tcp_t *)handle;
|
||||||
@@ -64,36 +67,37 @@ void EQ::Net::TCPConnection::Connect(const std::string &addr, int port, bool ipv
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void EQ::Net::TCPConnection::Start() {
|
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) {
|
{
|
||||||
|
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->base = new char[suggested_size];
|
||||||
buf->len = suggested_size;
|
buf->len = suggested_size;
|
||||||
}, [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TCPConnection *connection = (TCPConnection*)stream->data;
|
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;
|
||||||
|
|
||||||
if (nread > 0) {
|
if (nread > 0) {
|
||||||
connection->Read(buf->base, nread);
|
connection->Read(buf->base, nread);
|
||||||
|
|
||||||
if (buf->base) {
|
|
||||||
delete[] buf->base;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (nread == UV_EOF) {
|
else if (nread == UV_EOF) {
|
||||||
connection->Disconnect();
|
connection->Disconnect();
|
||||||
|
|
||||||
if (buf->base) {
|
|
||||||
delete[] buf->base;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (nread < 0) {
|
else if (nread < 0) {
|
||||||
connection->Disconnect();
|
connection->Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
if (buf->base) {
|
if (buf->len > 65536) {
|
||||||
delete [] buf->base;
|
delete [] buf->base;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EQ::Net::TCPConnection::OnRead(std::function<void(TCPConnection*, const unsigned char*, size_t)> cb)
|
void EQ::Net::TCPConnection::OnRead(std::function<void(TCPConnection*, const unsigned char*, size_t)> cb)
|
||||||
@@ -130,42 +134,91 @@ void EQ::Net::TCPConnection::Read(const char *data, size_t count)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EQ::Net::TCPConnection::Write(const char *data, size_t count)
|
void EQ::Net::TCPConnection::Write(const char* data, size_t count) {
|
||||||
{
|
if (!m_socket || !data || count == 0) {
|
||||||
if (!m_socket) {
|
std::cerr << "TCPConnection::Write - Invalid socket or data\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WriteBaton
|
if (count <= TCP_BUFFER_SIZE) {
|
||||||
{
|
// Fast path: use pooled request with embedded buffer
|
||||||
TCPConnection *connection;
|
auto req_opt = tcp_write_pool.acquire();
|
||||||
char *buffer;
|
if (!req_opt) {
|
||||||
};
|
std::cerr << "TCPConnection::Write - Out of write requests\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
WriteBaton *baton = new WriteBaton;
|
TCPWriteReq* write_req = *req_opt;
|
||||||
baton->connection = this;
|
|
||||||
baton->buffer = new char[count];
|
// 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;
|
uv_write_t* write_req = new uv_write_t;
|
||||||
memset(write_req, 0, sizeof(uv_write_t));
|
write_req->data = heap_buffer;
|
||||||
write_req->data = baton;
|
|
||||||
uv_buf_t send_buffers[1];
|
|
||||||
|
|
||||||
memcpy(baton->buffer, data, count);
|
uv_buf_t buf = uv_buf_init(heap_buffer, static_cast<unsigned int>(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) {
|
int result = uv_write(
|
||||||
WriteBaton *baton = (WriteBaton*)req->data;
|
write_req,
|
||||||
delete[] baton->buffer;
|
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;
|
delete req;
|
||||||
|
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
baton->connection->Disconnect();
|
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
|
std::string EQ::Net::TCPConnection::LocalIP() const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "tcp_connection_pooling.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
#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,6 +25,7 @@ public:
|
|||||||
uint8_t is_global;
|
uint8_t is_global;
|
||||||
uint32_t start_time;
|
uint32_t start_time;
|
||||||
uint32_t duration;
|
uint32_t duration;
|
||||||
|
uint64_t expire_at;
|
||||||
uint8_t never_expires;
|
uint8_t never_expires;
|
||||||
std::string notes;
|
std::string notes;
|
||||||
};
|
};
|
||||||
@@ -43,6 +44,7 @@ public:
|
|||||||
"is_global",
|
"is_global",
|
||||||
"start_time",
|
"start_time",
|
||||||
"duration",
|
"duration",
|
||||||
|
"expire_at",
|
||||||
"never_expires",
|
"never_expires",
|
||||||
"notes",
|
"notes",
|
||||||
};
|
};
|
||||||
@@ -57,6 +59,7 @@ public:
|
|||||||
"is_global",
|
"is_global",
|
||||||
"start_time",
|
"start_time",
|
||||||
"duration",
|
"duration",
|
||||||
|
"expire_at",
|
||||||
"never_expires",
|
"never_expires",
|
||||||
"notes",
|
"notes",
|
||||||
};
|
};
|
||||||
@@ -105,6 +108,7 @@ public:
|
|||||||
e.is_global = 0;
|
e.is_global = 0;
|
||||||
e.start_time = 0;
|
e.start_time = 0;
|
||||||
e.duration = 0;
|
e.duration = 0;
|
||||||
|
e.expire_at = 0;
|
||||||
e.never_expires = 0;
|
e.never_expires = 0;
|
||||||
e.notes = "";
|
e.notes = "";
|
||||||
|
|
||||||
@@ -149,8 +153,9 @@ public:
|
|||||||
e.is_global = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
|
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.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.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||||
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
|
e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
|
||||||
e.notes = row[7] ? row[7] : "";
|
e.never_expires = row[7] ? static_cast<uint8_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||||
|
e.notes = row[8] ? row[8] : "";
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -189,8 +194,9 @@ public:
|
|||||||
v.push_back(columns[3] + " = " + std::to_string(e.is_global));
|
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[4] + " = " + std::to_string(e.start_time));
|
||||||
v.push_back(columns[5] + " = " + std::to_string(e.duration));
|
v.push_back(columns[5] + " = " + std::to_string(e.duration));
|
||||||
v.push_back(columns[6] + " = " + std::to_string(e.never_expires));
|
v.push_back(columns[6] + " = " + std::to_string(e.expire_at));
|
||||||
v.push_back(columns[7] + " = '" + Strings::Escape(e.notes) + "'");
|
v.push_back(columns[7] + " = " + std::to_string(e.never_expires));
|
||||||
|
v.push_back(columns[8] + " = '" + Strings::Escape(e.notes) + "'");
|
||||||
|
|
||||||
auto results = db.QueryDatabase(
|
auto results = db.QueryDatabase(
|
||||||
fmt::format(
|
fmt::format(
|
||||||
@@ -218,6 +224,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.is_global));
|
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.start_time));
|
||||||
v.push_back(std::to_string(e.duration));
|
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(std::to_string(e.never_expires));
|
||||||
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
||||||
|
|
||||||
@@ -255,6 +262,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.is_global));
|
v.push_back(std::to_string(e.is_global));
|
||||||
v.push_back(std::to_string(e.start_time));
|
v.push_back(std::to_string(e.start_time));
|
||||||
v.push_back(std::to_string(e.duration));
|
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(std::to_string(e.never_expires));
|
||||||
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
||||||
|
|
||||||
@@ -296,8 +304,9 @@ public:
|
|||||||
e.is_global = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
|
e.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.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.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||||
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
|
e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
|
||||||
e.notes = row[7] ? row[7] : "";
|
e.never_expires = row[7] ? static_cast<uint8_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||||
|
e.notes = row[8] ? row[8] : "";
|
||||||
|
|
||||||
all_entries.push_back(e);
|
all_entries.push_back(e);
|
||||||
}
|
}
|
||||||
@@ -328,8 +337,9 @@ public:
|
|||||||
e.is_global = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
|
e.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.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.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
|
||||||
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
|
e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
|
||||||
e.notes = row[7] ? row[7] : "";
|
e.never_expires = row[7] ? static_cast<uint8_t>(strtoul(row[7], nullptr, 10)) : 0;
|
||||||
|
e.notes = row[8] ? row[8] : "";
|
||||||
|
|
||||||
all_entries.push_back(e);
|
all_entries.push_back(e);
|
||||||
}
|
}
|
||||||
@@ -410,6 +420,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.is_global));
|
v.push_back(std::to_string(e.is_global));
|
||||||
v.push_back(std::to_string(e.start_time));
|
v.push_back(std::to_string(e.start_time));
|
||||||
v.push_back(std::to_string(e.duration));
|
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(std::to_string(e.never_expires));
|
||||||
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
||||||
|
|
||||||
@@ -440,6 +451,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.is_global));
|
v.push_back(std::to_string(e.is_global));
|
||||||
v.push_back(std::to_string(e.start_time));
|
v.push_back(std::to_string(e.start_time));
|
||||||
v.push_back(std::to_string(e.duration));
|
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(std::to_string(e.never_expires));
|
||||||
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
v.push_back("'" + Strings::Escape(e.notes) + "'");
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public:
|
|||||||
int32_t id;
|
int32_t id;
|
||||||
int32_t start;
|
int32_t start;
|
||||||
int32_t duration;
|
int32_t duration;
|
||||||
|
uint32_t expire_at;
|
||||||
int16_t instance_id;
|
int16_t instance_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ public:
|
|||||||
"id",
|
"id",
|
||||||
"start",
|
"start",
|
||||||
"duration",
|
"duration",
|
||||||
|
"expire_at",
|
||||||
"instance_id",
|
"instance_id",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -46,6 +48,7 @@ public:
|
|||||||
"id",
|
"id",
|
||||||
"start",
|
"start",
|
||||||
"duration",
|
"duration",
|
||||||
|
"expire_at",
|
||||||
"instance_id",
|
"instance_id",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -90,6 +93,7 @@ public:
|
|||||||
e.id = 0;
|
e.id = 0;
|
||||||
e.start = 0;
|
e.start = 0;
|
||||||
e.duration = 0;
|
e.duration = 0;
|
||||||
|
e.expire_at = 0;
|
||||||
e.instance_id = 0;
|
e.instance_id = 0;
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
@@ -130,7 +134,8 @@ public:
|
|||||||
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
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.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
|
||||||
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
||||||
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
|
e.expire_at = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||||
|
e.instance_id = row[4] ? static_cast<int16_t>(atoi(row[4])) : 0;
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -167,7 +172,8 @@ public:
|
|||||||
v.push_back(columns[0] + " = " + std::to_string(e.id));
|
v.push_back(columns[0] + " = " + std::to_string(e.id));
|
||||||
v.push_back(columns[1] + " = " + std::to_string(e.start));
|
v.push_back(columns[1] + " = " + std::to_string(e.start));
|
||||||
v.push_back(columns[2] + " = " + std::to_string(e.duration));
|
v.push_back(columns[2] + " = " + std::to_string(e.duration));
|
||||||
v.push_back(columns[3] + " = " + std::to_string(e.instance_id));
|
v.push_back(columns[3] + " = " + std::to_string(e.expire_at));
|
||||||
|
v.push_back(columns[4] + " = " + std::to_string(e.instance_id));
|
||||||
|
|
||||||
auto results = db.QueryDatabase(
|
auto results = db.QueryDatabase(
|
||||||
fmt::format(
|
fmt::format(
|
||||||
@@ -192,6 +198,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.id));
|
v.push_back(std::to_string(e.id));
|
||||||
v.push_back(std::to_string(e.start));
|
v.push_back(std::to_string(e.start));
|
||||||
v.push_back(std::to_string(e.duration));
|
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));
|
v.push_back(std::to_string(e.instance_id));
|
||||||
|
|
||||||
auto results = db.QueryDatabase(
|
auto results = db.QueryDatabase(
|
||||||
@@ -225,6 +232,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.id));
|
v.push_back(std::to_string(e.id));
|
||||||
v.push_back(std::to_string(e.start));
|
v.push_back(std::to_string(e.start));
|
||||||
v.push_back(std::to_string(e.duration));
|
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));
|
v.push_back(std::to_string(e.instance_id));
|
||||||
|
|
||||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||||
@@ -262,7 +270,8 @@ public:
|
|||||||
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
||||||
e.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 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.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
||||||
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
|
e.expire_at = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||||
|
e.instance_id = row[4] ? static_cast<int16_t>(atoi(row[4])) : 0;
|
||||||
|
|
||||||
all_entries.push_back(e);
|
all_entries.push_back(e);
|
||||||
}
|
}
|
||||||
@@ -290,7 +299,8 @@ public:
|
|||||||
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
|
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.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
|
||||||
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
|
||||||
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
|
e.expire_at = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
|
||||||
|
e.instance_id = row[4] ? static_cast<int16_t>(atoi(row[4])) : 0;
|
||||||
|
|
||||||
all_entries.push_back(e);
|
all_entries.push_back(e);
|
||||||
}
|
}
|
||||||
@@ -368,6 +378,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.id));
|
v.push_back(std::to_string(e.id));
|
||||||
v.push_back(std::to_string(e.start));
|
v.push_back(std::to_string(e.start));
|
||||||
v.push_back(std::to_string(e.duration));
|
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));
|
v.push_back(std::to_string(e.instance_id));
|
||||||
|
|
||||||
auto results = db.QueryDatabase(
|
auto results = db.QueryDatabase(
|
||||||
@@ -394,6 +405,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.id));
|
v.push_back(std::to_string(e.id));
|
||||||
v.push_back(std::to_string(e.start));
|
v.push_back(std::to_string(e.start));
|
||||||
v.push_back(std::to_string(e.duration));
|
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));
|
v.push_back(std::to_string(e.instance_id));
|
||||||
|
|
||||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||||
|
|||||||
@@ -7,49 +7,11 @@
|
|||||||
|
|
||||||
class InstanceListRepository: public BaseInstanceListRepository {
|
class InstanceListRepository: public BaseInstanceListRepository {
|
||||||
public:
|
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)
|
static int UpdateDuration(Database& db, uint16 instance_id, uint32_t new_duration)
|
||||||
{
|
{
|
||||||
auto results = db.QueryDatabase(
|
auto results = db.QueryDatabase(
|
||||||
fmt::format(
|
fmt::format(
|
||||||
"UPDATE `{}` SET `duration` = {} WHERE `{}` = {}",
|
"UPDATE `{}` SET `duration` = {}, `expire_at` = (`duration` + `start_time`) WHERE `{}` = {}",
|
||||||
TableName(),
|
TableName(),
|
||||||
new_duration,
|
new_duration,
|
||||||
PrimaryKey(),
|
PrimaryKey(),
|
||||||
@@ -65,7 +27,7 @@ public:
|
|||||||
auto results = db.QueryDatabase(
|
auto results = db.QueryDatabase(
|
||||||
fmt::format(
|
fmt::format(
|
||||||
SQL(
|
SQL(
|
||||||
SELECT ((start_time + duration) - UNIX_TIMESTAMP()) AS `remaining` FROM `{}`
|
SELECT (`expire_at` - UNIX_TIMESTAMP()) AS `remaining` FROM `{}`
|
||||||
WHERE `id` = {}
|
WHERE `id` = {}
|
||||||
),
|
),
|
||||||
TableName(),
|
TableName(),
|
||||||
|
|||||||
@@ -8,47 +8,11 @@
|
|||||||
class RespawnTimesRepository: public BaseRespawnTimesRepository {
|
class RespawnTimesRepository: public BaseRespawnTimesRepository {
|
||||||
public:
|
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)
|
static void ClearExpiredRespawnTimers(Database& db)
|
||||||
{
|
{
|
||||||
db.QueryDatabase(
|
db.QueryDatabase(
|
||||||
fmt::format(
|
fmt::format(
|
||||||
"DELETE FROM `{}` WHERE (`start` + `duration`) < UNIX_TIMESTAMP(NOW())",
|
"DELETE FROM `{}` WHERE `expire_at` < UNIX_TIMESTAMP(NOW())",
|
||||||
TableName()
|
TableName()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -77,6 +41,11 @@ public:
|
|||||||
|
|
||||||
return ((r.start + r.duration) - time_seconds);
|
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
|
#endif //EQEMU_RESPAWN_TIMES_REPOSITORY_H
|
||||||
|
|||||||
@@ -54,8 +54,10 @@ public:
|
|||||||
{
|
{
|
||||||
BulkTraders_Struct all_entries{};
|
BulkTraders_Struct all_entries{};
|
||||||
std::vector<DistinctTraders_Struct> distinct_traders;
|
std::vector<DistinctTraders_Struct> distinct_traders;
|
||||||
|
MySQLRequestResult results;
|
||||||
|
|
||||||
auto results = db.QueryDatabase(fmt::format(
|
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 "
|
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
|
||||||
"FROM trader AS t "
|
"FROM trader AS t "
|
||||||
"JOIN character_data AS c ON t.char_id = c.id "
|
"JOIN character_data AS c ON t.char_id = c.id "
|
||||||
@@ -65,6 +67,18 @@ public:
|
|||||||
char_zone_instance_id,
|
char_zone_instance_id,
|
||||||
max_results)
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
distinct_traders.reserve(results.RowCount());
|
distinct_traders.reserve(results.RowCount());
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,75 @@
|
|||||||
|
|
||||||
class ZoneStateSpawnsRepository : public BaseZoneStateSpawnsRepository {
|
class ZoneStateSpawnsRepository : public BaseZoneStateSpawnsRepository {
|
||||||
public:
|
public:
|
||||||
// Custom extended repository methods here
|
static void PurgeInvalidZoneStates(Database &database)
|
||||||
|
{
|
||||||
|
std::string query = R"(
|
||||||
|
SELECT zone_id, instance_id
|
||||||
|
FROM zone_state_spawns
|
||||||
|
GROUP BY zone_id, instance_id
|
||||||
|
HAVING COUNT(*) = SUM(
|
||||||
|
CASE
|
||||||
|
WHEN hp = 0
|
||||||
|
AND mana = 0
|
||||||
|
AND endurance = 0
|
||||||
|
AND (loot_data IS NULL OR loot_data = '')
|
||||||
|
AND (entity_variables IS NULL OR entity_variables = '')
|
||||||
|
AND (buffs IS NULL OR buffs = '')
|
||||||
|
THEN 1 ELSE 0
|
||||||
|
END
|
||||||
|
);
|
||||||
|
)";
|
||||||
|
|
||||||
|
auto results = database.QueryDatabase(query);
|
||||||
|
if (!results.Success()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto row: results) {
|
||||||
|
uint32 zone_id = std::stoul(row[0]);
|
||||||
|
uint32 instance_id = std::stoul(row[1]);
|
||||||
|
|
||||||
|
int rows = ZoneStateSpawnsRepository::DeleteWhere(
|
||||||
|
database,
|
||||||
|
fmt::format(
|
||||||
|
"`zone_id` = {} AND `instance_id` = {}",
|
||||||
|
zone_id,
|
||||||
|
instance_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
LogInfo(
|
||||||
|
"Purged invalid zone state data for zone [{}] instance [{}] rows [{}]",
|
||||||
|
zone_id,
|
||||||
|
instance_id,
|
||||||
|
Strings::Commify(rows)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PurgeOldZoneStates(Database &database)
|
||||||
|
{
|
||||||
|
int days = RuleI(Zone, StateSaveClearDays);
|
||||||
|
|
||||||
|
std::string query = fmt::format(
|
||||||
|
"DELETE FROM zone_state_spawns WHERE created_at < NOW() - INTERVAL {} DAY",
|
||||||
|
days
|
||||||
|
);
|
||||||
|
|
||||||
|
auto results = database.QueryDatabase(query);
|
||||||
|
if (!results.Success()) {
|
||||||
|
LogError("Failed to purge old zone state data older than {} days.", days);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.RowsAffected() > 0) {
|
||||||
|
LogInfo(
|
||||||
|
"Purged old zone state data older than days [{}] rows [{}]",
|
||||||
|
days,
|
||||||
|
Strings::Commify(results.RowsAffected())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+17
-16
@@ -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, 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, 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, DuelCorpseResTime, 600000, "Time before cant res corpse after a duel (milliseconds) DEFAULT: 600000 (10 Minutes)")
|
||||||
RULE_INT(Character, CorpseOwnerOnlineTime, 30000, "How often corpse will check if its owner is online DEFAULT: 30000 (30 Seconds)")
|
RULE_INT(Character, CorpseOwnerOnlineCheckTime, 300, "How often corpse will check if its owner is online DEFAULT: 300 (5 minutes)")
|
||||||
RULE_BOOL(Character, LeaveCorpses, true, "Setting whether you leave a corpse behind")
|
RULE_BOOL(Character, LeaveCorpses, true, "Setting whether you leave a corpse behind")
|
||||||
RULE_BOOL(Character, LeaveNakedCorpses, false, "Setting whether you leave a corpse without items")
|
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")
|
RULE_INT(Character, MaxDraggedCorpses, 2, "Maximum number of corpses you can drag at once")
|
||||||
@@ -231,6 +231,7 @@ RULE_INT(Character, MendAlwaysSucceedValue, 199, "Value at which mend will alway
|
|||||||
RULE_BOOL(Character, SneakAlwaysSucceedOver100, false, "When sneak skill is over 100, always succeed sneak/hide. Default: false")
|
RULE_BOOL(Character, SneakAlwaysSucceedOver100, false, "When sneak skill is over 100, always succeed sneak/hide. Default: false")
|
||||||
RULE_INT(Character, BandolierSwapDelay, 0, "Bandolier swap delay in milliseconds, default is 0")
|
RULE_INT(Character, BandolierSwapDelay, 0, "Bandolier swap delay in milliseconds, default is 0")
|
||||||
RULE_BOOL(Character, EnableHackedFastCampForGM, false, "Enables hacked fast camp for GM clients, if the GM doesn't have a hacked client they'll camp like normal")
|
RULE_BOOL(Character, EnableHackedFastCampForGM, false, "Enables hacked fast camp for GM clients, if the GM doesn't have a hacked client they'll camp like normal")
|
||||||
|
RULE_BOOL(Character, AlwaysAllowNameChange, false, "Enable this option to allow /changename to work without enabling a name change via scripts.")
|
||||||
RULE_CATEGORY_END()
|
RULE_CATEGORY_END()
|
||||||
|
|
||||||
RULE_CATEGORY(Mercs)
|
RULE_CATEGORY(Mercs)
|
||||||
@@ -260,6 +261,7 @@ RULE_INT(Guild, TributeTime, 600000, "Time in ms for guild tributes. Default is
|
|||||||
RULE_INT(Guild, TributeTimeRefreshInterval, 180000, "Time in ms to send all guild members a Tribute Time refresh. Default is 3 mins.")
|
RULE_INT(Guild, 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_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, 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_END()
|
||||||
|
|
||||||
RULE_CATEGORY(Skills)
|
RULE_CATEGORY(Skills)
|
||||||
@@ -289,6 +291,7 @@ RULE_BOOL(Pets, ClientPetsUseOwnerNameInLastName, true, "Disable this to keep cl
|
|||||||
RULE_BOOL(Pets, CanTakeNoDrop, false, "Setting whether anyone can give no-drop items to pets")
|
RULE_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_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, 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_END()
|
||||||
|
|
||||||
RULE_CATEGORY(GM)
|
RULE_CATEGORY(GM)
|
||||||
@@ -344,6 +347,7 @@ 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, 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_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_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_END()
|
||||||
|
|
||||||
RULE_CATEGORY(Zone)
|
RULE_CATEGORY(Zone)
|
||||||
@@ -376,7 +380,9 @@ RULE_BOOL(Zone, AllowCrossZoneSpellsOnPets, false, "Set to true to allow cross z
|
|||||||
RULE_BOOL(Zone, ZoneShardQuestMenuOnly, false, "Set to true if you only want quests to show the zone shard menu")
|
RULE_BOOL(Zone, ZoneShardQuestMenuOnly, false, "Set to true if you only want quests to show the zone shard menu")
|
||||||
RULE_BOOL(Zone, StateSaveEntityVariables, true, "Set to true if you want buffs to be saved on shutdown")
|
RULE_BOOL(Zone, StateSaveEntityVariables, true, "Set to true if you want buffs to be saved on shutdown")
|
||||||
RULE_BOOL(Zone, StateSaveBuffs, true, "Set to true if you want buffs to be saved on shutdown")
|
RULE_BOOL(Zone, StateSaveBuffs, true, "Set to true if you want buffs to be saved on shutdown")
|
||||||
|
RULE_INT(Zone, StateSaveClearDays, 7, "Clears state save data older than this many days")
|
||||||
RULE_BOOL(Zone, StateSavingOnShutdown, true, "Set to true if you want zones to save state on shutdown (npcs, corpses, loot, entity variables, buffs etc.)")
|
RULE_BOOL(Zone, StateSavingOnShutdown, true, "Set to true if you want zones to save state on shutdown (npcs, corpses, loot, entity variables, buffs etc.)")
|
||||||
|
RULE_INT(Zone, UpdateWhoTimer, 120, "Seconds between updates to /who list, CLE stale timer")
|
||||||
RULE_CATEGORY_END()
|
RULE_CATEGORY_END()
|
||||||
|
|
||||||
RULE_CATEGORY(Map)
|
RULE_CATEGORY(Map)
|
||||||
@@ -386,9 +392,10 @@ RULE_BOOL(Map, MobZVisualDebug, false, "Displays spell effects determining wheth
|
|||||||
RULE_BOOL(Map, MobPathingVisualDebug, false, "Displays nodes in pathing points in realtime to help with visual debugging")
|
RULE_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_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_INT(Map, FindBestZHeightAdjust, 1, "Adds this to the current Z before seeking the best Z position")
|
||||||
RULE_BOOL(Map, CheckForLoSCheat, false, "Runs predefined zone checks to check for LoS cheating through doors and such.")
|
RULE_BOOL(Map, CheckForDoorLoSCheat, true, "Runs LoS checks to prevent cheating through doors.")
|
||||||
RULE_BOOL(Map, EnableLoSCheatExemptions, false, "Enables exemptions for the LoS Cheat check.")
|
RULE_BOOL(Map, EnableLoSCheatExemptions, false, "Enables exemptions for the LoS Cheat check. Must modify source to create these.")
|
||||||
RULE_REAL(Map, RangeCheckForLoSCheat, 20.0, "Default 20.0. Range to check if one is within range of a door.")
|
RULE_REAL(Map, RangeCheckForDoorLoSCheat, 250.0, "Default 250.0. Range to check if a door is blocking LoS from the target.")
|
||||||
|
RULE_STRING(Map, ZonesToCheckDoorCheat, "89,103", "Zones that will check for the door LoS cheat. You can leave it blank to disable, 'all' to check all zones or use a comma-delimited list of zones. Default Sebilis & Chardok")
|
||||||
RULE_CATEGORY_END()
|
RULE_CATEGORY_END()
|
||||||
|
|
||||||
RULE_CATEGORY(Pathing)
|
RULE_CATEGORY(Pathing)
|
||||||
@@ -812,6 +819,7 @@ RULE_INT(Bots, PercentChanceToCastDispel, 75, "The chance for a bot to attempt t
|
|||||||
RULE_INT(Bots, PercentChanceToCastInCombatBuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
|
RULE_INT(Bots, 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, 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, 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, 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, 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%.")
|
RULE_INT(Bots, PercentChanceToCastCure, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
|
||||||
@@ -823,8 +831,6 @@ RULE_INT(Bots, MinDelayBetweenInCombatCastAttempts, 500, "The minimum delay in m
|
|||||||
RULE_INT(Bots, MaxDelayBetweenInCombatCastAttempts, 2000, "The maximum delay in milliseconds between cast attempts while in-combat. This is rolled between the min and max. Default 2500ms.")
|
RULE_INT(Bots, 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, 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, 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, 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, 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.")
|
RULE_INT(Bots, MezFailDelay, 1250, "1250 (1.25 sec) Default. Delay between failed Mez attempts.")
|
||||||
@@ -834,14 +840,14 @@ RULE_INT(Bots, MinGroupCureTargets, 3, "Minimum number of targets in valid range
|
|||||||
RULE_INT(Bots, MinTargetsForAESpell, 3, "Minimum number of targets in valid range that are required for an AE spell to cast. Default 3.")
|
RULE_INT(Bots, MinTargetsForAESpell, 3, "Minimum number of targets in valid range that are required for an AE spell to cast. Default 3.")
|
||||||
RULE_INT(Bots, MinTargetsForGroupSpell, 3, "Minimum number of targets in valid range that are required for an group spell to cast. Default 3.")
|
RULE_INT(Bots, MinTargetsForGroupSpell, 3, "Minimum number of targets in valid range that are required for an group spell to cast. Default 3.")
|
||||||
RULE_BOOL(Bots, AllowBuffingHealingFamiliars, false, "Determines if bots are allowed to buff and heal familiars. Default false.")
|
RULE_BOOL(Bots, AllowBuffingHealingFamiliars, false, "Determines if bots are allowed to buff and heal familiars. Default false.")
|
||||||
RULE_BOOL(Bots, RunSpellTypeChecksOnSpawn, false, "This will run a serious of checks on spell types and output errors to LogBotSpellTypeChecks")
|
RULE_BOOL(Bots, RunSpellTypeChecksOnBoot, false, "This will run a series of checks to find potential errors in your bot_spells_entries table on boot and output to LogBotSpellTypeChecks")
|
||||||
RULE_BOOL(Bots, UseParentSpellTypeForChecks, true, "This will check only the parent instead of AE/Group/Pet types (ex: AENukes/AERains/PBAENukes fall under Nukes or PetBuffs fall under buffs) when RunSpellTypeChecksOnSpawn fires")
|
RULE_BOOL(Bots, UseParentSpellTypeForChecks, true, "This will check only the parent instead of AE/Group/Pet types (ex: AENukes/AERains/PBAENukes fall under Nukes or PetBuffs fall under buffs) when RunSpellTypeChecksOnSpawn fires")
|
||||||
RULE_BOOL(Bots, AllowForcedCastsBySpellID, true, "If enabled, players can use ^cast spellid # to cast a specific spell by ID that is in their spell list")
|
RULE_BOOL(Bots, AllowForcedCastsBySpellID, true, "If enabled, players can use ^cast spellid # to cast a specific spell by ID that is in their spell list")
|
||||||
RULE_BOOL(Bots, AllowCastAAs, true, "If enabled, players can use ^cast aa to cast a clickable AA")
|
RULE_BOOL(Bots, AllowCastAAs, true, "If enabled, players can use ^cast aa to cast a clickable AA")
|
||||||
RULE_BOOL(Bots, AllowMagicianEpicPet, false, "If enabled, magician bots can summon their epic pets following the rules AllowMagicianEpicPetLevel")
|
RULE_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, 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_INT(Bots, RequiredMagicianEpicPetItemID, 28034, "If AllowMagicianEpicPet is enabled and this is set, bots will be required to have this item ID equipped to cast their epic. Takes in to account AllowMagicianEpicPetLevel as well. Set to 0 to disable requirement")
|
||||||
RULE_STRING(Bots, EpicPetSpellName, "", "'teleport_zone' in the spell to be cast for epic pets. This must be in their spell list to cast.")
|
RULE_STRING(Bots, EpicPetSpellName, "", "'teleport_zone' in the spell to be cast for epic pets. This must be in their usable spell list to cast. Empty uses Manifest Elements - 'SumMageMultiElement'")
|
||||||
RULE_INT(Bots, ReclaimEnergySpellID, 331, "Spell ID for reclaim energy when using ^petsettype. Default 331")
|
RULE_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_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")
|
RULE_INT(Bots, PullSpellID, 5225, "Default 5225 - Throw Stone. Spell that will be cast to pull by bots")
|
||||||
@@ -852,15 +858,7 @@ 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_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, 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_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, 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, 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_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.")
|
RULE_BOOL(Bots, PreventBotCampOnFD, true, "True Default. If true, players will not be able to camp bots while feign death.")
|
||||||
@@ -901,6 +899,7 @@ RULE_STRING(Bots, ZonesWithForcedSpawnLimits, "", "Comma-delimited list of zones
|
|||||||
RULE_STRING(Bots, ZoneForcedSpawnLimits, "", "Comma-delimited list of forced spawn limits for 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, 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_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_END()
|
||||||
|
|
||||||
RULE_CATEGORY(Chat)
|
RULE_CATEGORY(Chat)
|
||||||
@@ -924,6 +923,7 @@ 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, 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, 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, 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_END()
|
||||||
|
|
||||||
RULE_CATEGORY(Merchant)
|
RULE_CATEGORY(Merchant)
|
||||||
@@ -1092,6 +1092,7 @@ RULE_CATEGORY(Instances)
|
|||||||
RULE_INT(Instances, ReservedInstances, 100, "Number of instance IDs which are reserved for globals. This value should not be changed while a server is running")
|
RULE_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_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, 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_END()
|
||||||
|
|
||||||
RULE_CATEGORY(Expedition)
|
RULE_CATEGORY(Expedition)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace ServerReload {
|
|||||||
LevelEXPMods,
|
LevelEXPMods,
|
||||||
Logs,
|
Logs,
|
||||||
Loot,
|
Loot,
|
||||||
|
Maps,
|
||||||
Merchants,
|
Merchants,
|
||||||
NPCEmotes,
|
NPCEmotes,
|
||||||
NPCSpells,
|
NPCSpells,
|
||||||
@@ -61,6 +62,7 @@ namespace ServerReload {
|
|||||||
"Level EXP Mods",
|
"Level EXP Mods",
|
||||||
"Logs",
|
"Logs",
|
||||||
"Loot",
|
"Loot",
|
||||||
|
"Maps",
|
||||||
"Merchants",
|
"Merchants",
|
||||||
"NPC Emotes",
|
"NPC Emotes",
|
||||||
"NPC Spells",
|
"NPC Spells",
|
||||||
|
|||||||
+19
-19
@@ -1470,9 +1470,10 @@ bool IsFastHealSpell(uint16 spell_id) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spell_id && IsValidSpell(spell_id)) {
|
if (IsValidSpell(spell_id)) {
|
||||||
if (
|
if (
|
||||||
spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME &&
|
spell_id != SPELL_MINOR_HEALING &&
|
||||||
|
(spells[spell_id].cast_time > MAX_VERY_FAST_HEAL_CASTING_TIME && spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME) &&
|
||||||
spells[spell_id].good_effect &&
|
spells[spell_id].good_effect &&
|
||||||
!IsGroupSpell(spell_id)
|
!IsGroupSpell(spell_id)
|
||||||
) {
|
) {
|
||||||
@@ -1509,8 +1510,9 @@ bool IsVeryFastHealSpell(uint16 spell_id)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spell_id) {
|
if (IsValidSpell(spell_id)) {
|
||||||
if (
|
if (
|
||||||
|
spell_id != SPELL_MINOR_HEALING &&
|
||||||
spells[spell_id].cast_time <= MAX_VERY_FAST_HEAL_CASTING_TIME &&
|
spells[spell_id].cast_time <= MAX_VERY_FAST_HEAL_CASTING_TIME &&
|
||||||
spells[spell_id].good_effect &&
|
spells[spell_id].good_effect &&
|
||||||
!IsGroupSpell(spell_id)
|
!IsGroupSpell(spell_id)
|
||||||
@@ -1548,8 +1550,13 @@ bool IsRegularSingleTargetHealSpell(uint16 spell_id)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spell_id) {
|
if (IsValidSpell(spell_id)) {
|
||||||
|
if (spell_id == SPELL_MINOR_HEALING) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
spells[spell_id].cast_time > MAX_FAST_HEAL_CASTING_TIME &&
|
||||||
spells[spell_id].target_type == ST_Target &&
|
spells[spell_id].target_type == ST_Target &&
|
||||||
!IsCompleteHealSpell(spell_id) &&
|
!IsCompleteHealSpell(spell_id) &&
|
||||||
!IsHealOverTimeSpell(spell_id) &&
|
!IsHealOverTimeSpell(spell_id) &&
|
||||||
@@ -1589,9 +1596,14 @@ bool IsRegularPetHealSpell(uint16 spell_id)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spell_id && IsValidSpell(spell_id)) {
|
if (IsValidSpell(spell_id)) {
|
||||||
|
if (spell_id == SPELL_MINOR_HEALING) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_Undead) &&
|
spells[spell_id].cast_time > MAX_FAST_HEAL_CASTING_TIME &&
|
||||||
|
(spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_SummonedPet) &&
|
||||||
!IsCompleteHealSpell(spell_id) &&
|
!IsCompleteHealSpell(spell_id) &&
|
||||||
!IsHealOverTimeSpell(spell_id) &&
|
!IsHealOverTimeSpell(spell_id) &&
|
||||||
!IsGroupSpell(spell_id)
|
!IsGroupSpell(spell_id)
|
||||||
@@ -1630,7 +1642,7 @@ bool IsRegularGroupHealSpell(uint16 spell_id)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spell_id) {
|
if (IsValidSpell(spell_id)) {
|
||||||
if (
|
if (
|
||||||
IsGroupSpell(spell_id) &&
|
IsGroupSpell(spell_id) &&
|
||||||
!IsCompleteHealSpell(spell_id) &&
|
!IsCompleteHealSpell(spell_id) &&
|
||||||
@@ -2796,18 +2808,6 @@ bool IsLichSpell(uint16 spell_id)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsValidSpellAndLoS(uint32 spell_id, bool has_los) {
|
|
||||||
if (!IsValidSpell(spell_id)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!has_los && IsTargetRequiredForSpell(spell_id)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsInstantHealSpell(uint32 spell_id) {
|
bool IsInstantHealSpell(uint32 spell_id) {
|
||||||
if (!IsValidSpell(spell_id)) {
|
if (!IsValidSpell(spell_id)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+3
-1
@@ -215,6 +215,7 @@
|
|||||||
#define SPELL_AMPLIFICATION 2603
|
#define SPELL_AMPLIFICATION 2603
|
||||||
#define SPELL_DIVINE_REZ 2738
|
#define SPELL_DIVINE_REZ 2738
|
||||||
#define SPELL_NATURES_RECOVERY 2520
|
#define SPELL_NATURES_RECOVERY 2520
|
||||||
|
#define SPELL_MINOR_HEALING 200
|
||||||
#define SPELL_ADRENALINE_SWELL 14445
|
#define SPELL_ADRENALINE_SWELL 14445
|
||||||
#define SPELL_ADRENALINE_SWELL_RK2 14446
|
#define SPELL_ADRENALINE_SWELL_RK2 14446
|
||||||
#define SPELL_ADRENALINE_SWELL_RK3 14447
|
#define SPELL_ADRENALINE_SWELL_RK3 14447
|
||||||
@@ -916,6 +917,8 @@ bool IsCommandedBotSpellType(uint16 spell_type);
|
|||||||
bool IsPullingBotSpellType(uint16 spell_type);
|
bool IsPullingBotSpellType(uint16 spell_type);
|
||||||
uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id);
|
uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id);
|
||||||
uint16 GetPetBotSpellType(uint16 spell_type);
|
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..
|
// These should not be used to determine spell category..
|
||||||
// They are a graphical affects (effects?) index only
|
// They are a graphical affects (effects?) index only
|
||||||
@@ -1813,7 +1816,6 @@ bool IsEffectInSpell(uint16 spell_id, int effect_id);
|
|||||||
uint16 GetSpellTriggerSpellID(uint16 spell_id, int effect_id);
|
uint16 GetSpellTriggerSpellID(uint16 spell_id, int effect_id);
|
||||||
bool IsBlankSpellEffect(uint16 spell_id, int effect_index);
|
bool IsBlankSpellEffect(uint16 spell_id, int effect_index);
|
||||||
bool IsValidSpell(uint32 spell_id);
|
bool IsValidSpell(uint32 spell_id);
|
||||||
bool IsValidSpellAndLoS(uint32 spell_id, bool has_los = true);
|
|
||||||
bool IsSummonSpell(uint16 spell_id);
|
bool IsSummonSpell(uint16 spell_id);
|
||||||
bool IsDamageSpell(uint16 spell_id);
|
bool IsDamageSpell(uint16 spell_id);
|
||||||
bool IsAnyDamageSpell(uint16 spell_id);
|
bool IsAnyDamageSpell(uint16 spell_id);
|
||||||
|
|||||||
+42
-254
@@ -1,4 +1,5 @@
|
|||||||
#include "spdat.h"
|
#include "spdat.h"
|
||||||
|
#include "../zone/bot.h"
|
||||||
|
|
||||||
bool IsBotSpellTypeDetrimental(uint16 spell_type) {
|
bool IsBotSpellTypeDetrimental(uint16 spell_type) {
|
||||||
switch (spell_type) {
|
switch (spell_type) {
|
||||||
@@ -417,264 +418,23 @@ uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id) {
|
|||||||
return UINT16_MAX;
|
return UINT16_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16 correct_type = UINT16_MAX;
|
uint16 correct_type = spell_type;
|
||||||
SPDat_Spell_Struct spell = spells[spell_id];
|
|
||||||
std::string teleport_zone = spell.teleport_zone;
|
|
||||||
|
|
||||||
if (IsCharmSpell(spell_id)) {
|
if (!Bot::IsValidSpellTypeBySpellID(spell_type, spell_id)) {
|
||||||
correct_type = BotSpellTypes::Charm;
|
correct_type = UINT16_MAX;
|
||||||
}
|
|
||||||
else if (IsFearSpell(spell_id)) {
|
auto start = std::min({ BotSpellTypes::START, BotSpellTypes::COMMANDED_START, BotSpellTypes::DISCIPLINE_START });
|
||||||
correct_type = BotSpellTypes::Fear;
|
auto end = std::max({ BotSpellTypes::END, BotSpellTypes::COMMANDED_END, BotSpellTypes::DISCIPLINE_END });
|
||||||
}
|
|
||||||
else if (IsEffectInSpell(spell_id, SE_Revive)) {
|
for (int i = end; i >= start; --i) {
|
||||||
correct_type = BotSpellTypes::Resurrect;
|
if (!Bot::IsValidBotSpellType(i) || i == BotSpellTypes::InCombatBuff) {
|
||||||
}
|
continue;
|
||||||
else if (IsHarmonySpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::Lull;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
teleport_zone.compare("") &&
|
|
||||||
!IsEffectInSpell(spell_id, SE_GateToHomeCity) &&
|
|
||||||
IsBeneficialSpell(spell_id) &&
|
|
||||||
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::Teleport;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
IsBeneficialSpell(spell_id) &&
|
|
||||||
IsEffectInSpell(spell_id, SE_Succor)
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::Succor;
|
|
||||||
}
|
|
||||||
else if (IsEffectInSpell(spell_id, SE_BindAffinity)) {
|
|
||||||
correct_type = BotSpellTypes::BindAffinity;
|
|
||||||
}
|
|
||||||
else if (IsEffectInSpell(spell_id, SE_Identify)) {
|
|
||||||
correct_type = BotSpellTypes::Identify;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
spell_type == BotSpellTypes::Levitate &&
|
|
||||||
IsBeneficialSpell(spell_id) &&
|
|
||||||
IsEffectInSpell(spell_id, SE_Levitate)
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::Levitate;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
spell_type == BotSpellTypes::Rune &&
|
|
||||||
IsBeneficialSpell(spell_id) &&
|
|
||||||
(IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune))
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::Rune;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
spell_type == BotSpellTypes::WaterBreathing &&
|
|
||||||
IsBeneficialSpell(spell_id) &&
|
|
||||||
IsEffectInSpell(spell_id, SE_WaterBreathing)
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::WaterBreathing;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
spell_type == BotSpellTypes::Size &&
|
|
||||||
IsBeneficialSpell(spell_id) &&
|
|
||||||
(IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::Size;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
spell_type == BotSpellTypes::Invisibility &&
|
|
||||||
IsBeneficialSpell(spell_id) &&
|
|
||||||
(IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id))
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::Invisibility;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
spell_type == BotSpellTypes::MovementSpeed &&
|
|
||||||
IsBeneficialSpell(spell_id) &&
|
|
||||||
IsEffectInSpell(spell_id, SE_MovementSpeed)
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::MovementSpeed;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
!teleport_zone.compare("") &&
|
|
||||||
IsBeneficialSpell(spell_id) &&
|
|
||||||
(IsEffectInSpell(spell_id, SE_Translocate) || IsEffectInSpell(spell_id, SE_GateToHomeCity))
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::SendHome;
|
|
||||||
}
|
|
||||||
else if (IsEffectInSpell(spell_id, SE_SummonCorpse)) {
|
|
||||||
correct_type = BotSpellTypes::SummonCorpse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (correct_type == UINT16_MAX) {
|
if (Bot::IsValidSpellTypeBySpellID(i, spell_id)) {
|
||||||
if (
|
correct_type = i;
|
||||||
IsSummonPetSpell(spell_id) ||
|
|
||||||
IsEffectInSpell(spell_id, SE_TemporaryPets)
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::Pet;
|
|
||||||
}
|
|
||||||
else if (IsMesmerizeSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::Mez;
|
|
||||||
}
|
|
||||||
else if (IsEscapeSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::Escape;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
IsDetrimentalSpell(spell_id) &&
|
|
||||||
IsEffectInSpell(spell_id, SE_Root)
|
|
||||||
) {
|
|
||||||
if (IsAnyAESpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::AERoot;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
correct_type = BotSpellTypes::Root;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
IsDetrimentalSpell(spell_id) &&
|
|
||||||
IsLifetapSpell(spell_id)
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::Lifetap;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
IsDetrimentalSpell(spell_id) &&
|
|
||||||
IsEffectInSpell(spell_id, SE_MovementSpeed)
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::Snare;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
IsDetrimentalSpell(spell_id) &&
|
|
||||||
(IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id))
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::DOT;
|
|
||||||
}
|
|
||||||
else if (IsDispelSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::Dispel;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
IsDetrimentalSpell(spell_id) &&
|
|
||||||
IsSlowSpell(spell_id)
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::Slow;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
IsDebuffSpell(spell_id) &&
|
|
||||||
!IsHateReduxSpell(spell_id) &&
|
|
||||||
!IsHateSpell(spell_id)
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::Debuff;
|
|
||||||
}
|
|
||||||
else if (IsHateReduxSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::HateRedux;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
IsDetrimentalSpell(spell_id) &&
|
|
||||||
IsHateSpell(spell_id)
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::HateLine;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
IsBuffSpell(spell_id) &&
|
|
||||||
IsBeneficialSpell(spell_id) &&
|
|
||||||
IsBardSong(spell_id)
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
spell_type == BotSpellTypes::InCombatBuffSong ||
|
|
||||||
spell_type == BotSpellTypes::OutOfCombatBuffSong ||
|
|
||||||
spell_type == BotSpellTypes::PreCombatBuffSong
|
|
||||||
) {
|
|
||||||
correct_type = spell_type;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
correct_type = BotSpellTypes::OutOfCombatBuffSong;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
!IsBardSong(spell_id) &&
|
|
||||||
(
|
|
||||||
(IsSelfConversionSpell(spell_id) && spell.buff_duration < 1) ||
|
|
||||||
(spell_type == BotSpellTypes::InCombatBuff && IsAnyBuffSpell(spell_id))
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::InCombatBuff;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
spell_type == BotSpellTypes::PreCombatBuff &&
|
|
||||||
IsAnyBuffSpell(spell_id) &&
|
|
||||||
!IsBardSong(spell_id)
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::PreCombatBuff;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
(IsCureSpell(spell_id) && spell_type == BotSpellTypes::Cure) ||
|
|
||||||
(IsCureSpell(spell_id) && !IsAnyHealSpell(spell_id))
|
|
||||||
) {
|
|
||||||
correct_type = BotSpellTypes::Cure;
|
|
||||||
}
|
|
||||||
else if (IsAnyNukeOrStunSpell(spell_id)) {
|
|
||||||
if (IsAnyAESpell(spell_id)) {
|
|
||||||
if (IsAERainSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::AERains;
|
|
||||||
}
|
|
||||||
else if (IsPBAENukeSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::PBAENuke;
|
|
||||||
}
|
|
||||||
else if (IsStunSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::AEStun;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
correct_type = BotSpellTypes::AENukes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (IsStunSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::Stun;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
correct_type = BotSpellTypes::Nuke;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (IsAnyHealSpell(spell_id)) {
|
|
||||||
if (IsGroupSpell(spell_id)) {
|
|
||||||
if (IsGroupCompleteHealSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::GroupCompleteHeals;
|
|
||||||
}
|
|
||||||
else if (IsGroupHealOverTimeSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::GroupHoTHeals;
|
|
||||||
}
|
|
||||||
else if (IsRegularGroupHealSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::GroupHeals;
|
|
||||||
}
|
|
||||||
|
|
||||||
return correct_type;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
if (IsVeryFastHealSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::VeryFastHeals;
|
|
||||||
}
|
|
||||||
else if (IsFastHealSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::FastHeals;
|
|
||||||
}
|
|
||||||
else if (IsCompleteHealSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::CompleteHeal;
|
|
||||||
}
|
|
||||||
else if (IsHealOverTimeSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::HoTHeals;
|
|
||||||
}
|
|
||||||
else if (IsRegularSingleTargetHealSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::RegularHeal;
|
|
||||||
}
|
|
||||||
else if (IsRegularPetHealSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::RegularHeal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (IsAnyBuffSpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::Buff;
|
|
||||||
|
|
||||||
if (IsResistanceOnlySpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::ResistBuffs;
|
|
||||||
}
|
|
||||||
else if (IsDamageShieldOnlySpell(spell_id)) {
|
|
||||||
correct_type = BotSpellTypes::DamageShields;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -708,3 +468,31 @@ uint16 GetPetBotSpellType(uint16 spell_type) {
|
|||||||
|
|
||||||
return spell_type;
|
return spell_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsBotBuffSpellType(uint16 spell_type) {
|
||||||
|
switch (spell_type) {
|
||||||
|
case BotSpellTypes::Buff:
|
||||||
|
case BotSpellTypes::PetBuffs:
|
||||||
|
case BotSpellTypes::ResistBuffs:
|
||||||
|
case BotSpellTypes::PetResistBuffs:
|
||||||
|
case BotSpellTypes::DamageShields:
|
||||||
|
case BotSpellTypes::PetDamageShields:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BotRequiresLoSToCast(uint16 spell_type, uint16 spell_id) {
|
||||||
|
if (!BotSpellTypeRequiresTarget(spell_type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsTargetRequiredForSpell(spell_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -196,3 +196,25 @@ const uint32 Timer::SetCurrentTime()
|
|||||||
return current_time;
|
return current_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uint32 Timer::RollForward(uint32 seconds)
|
||||||
|
{
|
||||||
|
struct timeval read_time{};
|
||||||
|
uint32 this_time;
|
||||||
|
|
||||||
|
gettimeofday(&read_time, nullptr);
|
||||||
|
this_time = read_time.tv_sec * 1000 + read_time.tv_usec / 1000;
|
||||||
|
|
||||||
|
if (last_time == 0) {
|
||||||
|
current_time = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
current_time += this_time - last_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_time = this_time;
|
||||||
|
|
||||||
|
// Roll forward the specified number of seconds (converted to milliseconds)
|
||||||
|
current_time += seconds * 1000;
|
||||||
|
|
||||||
|
return current_time;
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public:
|
|||||||
inline uint32 GetDuration() { return(timer_time); }
|
inline uint32 GetDuration() { return(timer_time); }
|
||||||
|
|
||||||
static const uint32 SetCurrentTime();
|
static const uint32 SetCurrentTime();
|
||||||
|
static const uint32 RollForward(uint32 seconds);
|
||||||
static const uint32 GetCurrentTime();
|
static const uint32 GetCurrentTime();
|
||||||
static const uint32 GetTimeSeconds();
|
static const uint32 GetTimeSeconds();
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
// Build variables
|
// Build variables
|
||||||
// these get injected during the build pipeline
|
// these get injected during the build pipeline
|
||||||
#define CURRENT_VERSION "23.3.1-dev" // always append -dev to the current version for custom-builds
|
#define CURRENT_VERSION "23.5.0-dev" // always append -dev to the current version for custom-builds
|
||||||
#define LOGIN_VERSION "0.8.0"
|
#define LOGIN_VERSION "0.8.0"
|
||||||
#define COMPILE_DATE __DATE__
|
#define COMPILE_DATE __DATE__
|
||||||
#define COMPILE_TIME __TIME__
|
#define COMPILE_TIME __TIME__
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define CURRENT_BINARY_DATABASE_VERSION 9311
|
#define CURRENT_BINARY_DATABASE_VERSION 9321
|
||||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
|
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "eqemu-server",
|
"name": "eqemu-server",
|
||||||
"version": "23.3.1",
|
"version": "23.5.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/EQEmu/Server.git"
|
"url": "https://github.com/EQEmu/Server.git"
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ int main()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (player_event_process_timer.Check()) {
|
if (player_event_process_timer.Check()) {
|
||||||
std::jthread player_event_thread(&PlayerEventLogs::Process, &player_event_logs);
|
player_event_logs.Process();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -177,7 +177,7 @@ void ChatChannelList::SendAllChannels(Client *c) {
|
|||||||
|
|
||||||
std::string Message;
|
std::string Message;
|
||||||
|
|
||||||
char CountString[10];
|
char CountString[13];
|
||||||
|
|
||||||
while(iterator.MoreElements()) {
|
while(iterator.MoreElements()) {
|
||||||
|
|
||||||
@@ -408,7 +408,7 @@ void ChatChannel::SendChannelMembers(Client *c) {
|
|||||||
|
|
||||||
if(!c) return;
|
if(!c) return;
|
||||||
|
|
||||||
char CountString[10];
|
char CountString[13];
|
||||||
|
|
||||||
sprintf(CountString, "(%i)", MemberCount(c->GetAccountStatus()));
|
sprintf(CountString, "(%i)", MemberCount(c->GetAccountStatus()));
|
||||||
|
|
||||||
|
|||||||
@@ -746,3 +746,6 @@ OP_TradeSkillRecipeInspect=0x4f7e
|
|||||||
OP_InvokeChangePetNameImmediate=0x046d
|
OP_InvokeChangePetNameImmediate=0x046d
|
||||||
OP_InvokeChangePetName=0x4506
|
OP_InvokeChangePetName=0x4506
|
||||||
OP_ChangePetName=0x5dab
|
OP_ChangePetName=0x5dab
|
||||||
|
|
||||||
|
OP_InvokeNameChangeImmediate=0x4fe2
|
||||||
|
OP_InvokeNameChangeLazy=0x2f2e
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ echo "# Running NPC hand-in tests"
|
|||||||
./bin/zone tests:npc-handins 2>&1 | tee test_output.log
|
./bin/zone tests:npc-handins 2>&1 | tee test_output.log
|
||||||
./bin/zone tests:npc-handins-multiquest 2>&1 | tee -a test_output.log
|
./bin/zone tests:npc-handins-multiquest 2>&1 | tee -a test_output.log
|
||||||
./bin/zone tests:databuckets 2>&1 | tee -a test_output.log
|
./bin/zone tests:databuckets 2>&1 | tee -a test_output.log
|
||||||
|
./bin/zone tests:zone-state 2>&1 | tee -a test_output.log
|
||||||
|
|
||||||
if grep -E -q "QueryErr|Error|FAILED" test_output.log; then
|
if grep -E -q "QueryErr|Error|FAILED" test_output.log; then
|
||||||
echo "Error found in test output! Failing build."
|
echo "Error found in test output! Failing build."
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/golang/protobuf v1.3.2 // indirect
|
github.com/golang/protobuf v1.3.2 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
golang.org/x/crypto v0.31.0 // indirect
|
golang.org/x/crypto v0.35.0 // indirect
|
||||||
golang.org/x/net v0.33.0 // indirect
|
golang.org/x/net v0.36.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
|
|||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ 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 --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 --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 --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
|
# generate "create_" table files
|
||||||
@@ -45,7 +44,6 @@ 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 --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 --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 --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
|
# with content
|
||||||
bash -c "${world_bin} database:dump --content-tables --dump-output-to-console > ${dump_path}create_tables_content.sql"
|
bash -c "${world_bin} database:dump --content-tables --dump-output-to-console > ${dump_path}create_tables_content.sql"
|
||||||
|
|||||||
+142
-30
@@ -42,11 +42,16 @@ extern ZSList zoneserver_list;
|
|||||||
uint32 numplayers = 0; //this really wants to be a member variable of ClientList...
|
uint32 numplayers = 0; //this really wants to be a member variable of ClientList...
|
||||||
|
|
||||||
ClientList::ClientList()
|
ClientList::ClientList()
|
||||||
: CLStale_timer(10000)
|
: CLStale_timer(10000),
|
||||||
|
m_poll_cache_timer(6000)
|
||||||
{
|
{
|
||||||
NextCLEID = 1;
|
NextCLEID = 1;
|
||||||
|
|
||||||
m_tick = std::make_unique<EQ::Timer>(5000, true, std::bind(&ClientList::OnTick, this, std::placeholders::_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() {
|
ClientList::~ClientList() {
|
||||||
@@ -57,6 +62,10 @@ void ClientList::Process() {
|
|||||||
if (CLStale_timer.Check())
|
if (CLStale_timer.Check())
|
||||||
CLCheckStale();
|
CLCheckStale();
|
||||||
|
|
||||||
|
if (m_poll_cache_timer.Check()) {
|
||||||
|
RebuildZoneServerCaches();
|
||||||
|
}
|
||||||
|
|
||||||
LinkedListIterator<Client*> iterator(list);
|
LinkedListIterator<Client*> iterator(list);
|
||||||
|
|
||||||
iterator.Reset();
|
iterator.Reset();
|
||||||
@@ -384,6 +393,7 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
cle->Update(zoneserver, scl);
|
cle->Update(zoneserver, scl);
|
||||||
|
AddToZoneServerCaches(cle);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -458,6 +468,7 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
|
|||||||
);
|
);
|
||||||
|
|
||||||
clientlist.Insert(cle);
|
clientlist.Insert(cle);
|
||||||
|
AddToZoneServerCaches(cle);
|
||||||
zoneserver->ChangeWID(scl->charid, cle->GetID());
|
zoneserver->ChangeWID(scl->charid, cle->GetID());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1608,7 +1619,7 @@ void ClientList::OnTick(EQ::Timer *t)
|
|||||||
/**
|
/**
|
||||||
* @param response
|
* @param response
|
||||||
*/
|
*/
|
||||||
void ClientList::GetClientList(Json::Value &response)
|
void ClientList::GetClientList(Json::Value &response, bool full_list)
|
||||||
{
|
{
|
||||||
LinkedListIterator<ClientListEntry *> Iterator(clientlist);
|
LinkedListIterator<ClientListEntry *> Iterator(clientlist);
|
||||||
|
|
||||||
@@ -1619,24 +1630,53 @@ void ClientList::GetClientList(Json::Value &response)
|
|||||||
|
|
||||||
Json::Value row;
|
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_id"] = cle->AccountID();
|
||||||
row["account_name"] = cle->AccountName();
|
row["account_name"] = cle->AccountName();
|
||||||
row["admin"] = cle->Admin();
|
row["character_id"] = cle->CharID();
|
||||||
row["id"] = cle->GetID();
|
row["anon"] = cle->Anon();
|
||||||
row["ip"] = cle->GetIP();
|
row["guild_id"] = cle->GuildID();
|
||||||
|
|
||||||
|
if (full_list) {
|
||||||
row["loginserver_account_id"] = cle->LSAccountID();
|
row["loginserver_account_id"] = cle->LSAccountID();
|
||||||
row["loginserver_id"] = cle->LSID();
|
row["loginserver_id"] = cle->LSID();
|
||||||
row["loginserver_name"] = cle->LSName();
|
row["loginserver_name"] = cle->LSName();
|
||||||
row["online"] = cle->Online();
|
row["online"] = cle->Online();
|
||||||
row["world_admin"] = cle->WorldAdmin();
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
auto server = cle->Server();
|
auto server = cle->Server();
|
||||||
if (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_address"] = server->GetCAddress();
|
||||||
row["server"]["client_local_address"] = server->GetCLocalAddress();
|
row["server"]["client_local_address"] = server->GetCLocalAddress();
|
||||||
row["server"]["client_port"] = server->GetCPort();
|
row["server"]["client_port"] = server->GetCPort();
|
||||||
row["server"]["compile_time"] = server->GetCompileTime();
|
row["server"]["compile_time"] = server->GetCompileTime();
|
||||||
row["server"]["id"] = server->GetID();
|
|
||||||
row["server"]["instance_id"] = server->GetInstanceID();
|
row["server"]["instance_id"] = server->GetInstanceID();
|
||||||
row["server"]["ip"] = server->GetIP();
|
row["server"]["ip"] = server->GetIP();
|
||||||
row["server"]["is_booting"] = server->IsBootingUp();
|
row["server"]["is_booting"] = server->IsBootingUp();
|
||||||
@@ -1647,34 +1687,11 @@ void ClientList::GetClientList(Json::Value &response)
|
|||||||
row["server"]["previous_zone_id"] = server->GetPrevZoneID();
|
row["server"]["previous_zone_id"] = server->GetPrevZoneID();
|
||||||
row["server"]["static_zone"] = server->IsStaticZone();
|
row["server"]["static_zone"] = server->IsStaticZone();
|
||||||
row["server"]["uui"] = server->GetUUID();
|
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 {
|
else {
|
||||||
row["server"] = Json::Value();
|
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);
|
response.append(row);
|
||||||
|
|
||||||
@@ -1850,3 +1867,98 @@ std::map<uint32, ClientListEntry *> ClientList::GetGuildClientsWithTributeOptIn(
|
|||||||
}
|
}
|
||||||
return guild_members;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+15
-1
@@ -66,7 +66,7 @@ public:
|
|||||||
int GetClientCount();
|
int GetClientCount();
|
||||||
void GetClients(const char *zone_name, std::vector<ClientListEntry *> &into);
|
void GetClients(const char *zone_name, std::vector<ClientListEntry *> &into);
|
||||||
|
|
||||||
void GetClientList(Json::Value &response);
|
void GetClientList(Json::Value &response, bool full_list = false);
|
||||||
void GetGuildClientList(Json::Value& response, uint32 guild_id);
|
void GetGuildClientList(Json::Value& response, uint32 guild_id);
|
||||||
|
|
||||||
void SendCharacterMessage(uint32_t character_id, int chat_type, const std::string& message);
|
void SendCharacterMessage(uint32_t character_id, int chat_type, const std::string& message);
|
||||||
@@ -76,6 +76,15 @@ public:
|
|||||||
void SendCharacterMessageID(const std::string& character_name, int chat_type, int eqstr_id, std::initializer_list<std::string> args = {});
|
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 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:
|
private:
|
||||||
void OnTick(EQ::Timer *t);
|
void OnTick(EQ::Timer *t);
|
||||||
inline uint32 GetNextCLEID() { return NextCLEID++; }
|
inline uint32 GetNextCLEID() { return NextCLEID++; }
|
||||||
@@ -90,6 +99,11 @@ private:
|
|||||||
|
|
||||||
|
|
||||||
std::unique_ptr<EQ::Timer> m_tick;
|
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_*/
|
#endif /*CLIENTLIST_H_*/
|
||||||
|
|||||||
@@ -137,8 +137,11 @@ void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining)
|
|||||||
m_expire_time = now + new_remaining;
|
m_expire_time = now + new_remaining;
|
||||||
m_duration = std::chrono::duration_cast<std::chrono::seconds>(m_expire_time - m_start_time);
|
m_duration = std::chrono::duration_cast<std::chrono::seconds>(m_expire_time - m_start_time);
|
||||||
|
|
||||||
InstanceListRepository::UpdateDuration(database,
|
InstanceListRepository::UpdateDuration(
|
||||||
GetInstanceID(), static_cast<uint32_t>(m_duration.count()));
|
database,
|
||||||
|
GetInstanceID(),
|
||||||
|
static_cast<uint32_t>(m_duration.count())
|
||||||
|
);
|
||||||
|
|
||||||
SendZonesDurationUpdate(); // update zone caches and actual instance's timer
|
SendZonesDurationUpdate(); // update zone caches and actual instance's timer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ void callGetZoneList(Json::Value &response)
|
|||||||
row["client_address"] = zone->GetCAddress();
|
row["client_address"] = zone->GetCAddress();
|
||||||
row["client_local_address"] = zone->GetCLocalAddress();
|
row["client_local_address"] = zone->GetCLocalAddress();
|
||||||
row["client_port"] = zone->GetCPort();
|
row["client_port"] = zone->GetCPort();
|
||||||
|
row["compile_version"] = zone->GetCurrentVersion();
|
||||||
|
row["compile_date"] = zone->GetCompileDate();
|
||||||
row["compile_time"] = zone->GetCompileTime();
|
row["compile_time"] = zone->GetCompileTime();
|
||||||
row["id"] = zone->GetID();
|
row["id"] = zone->GetID();
|
||||||
row["instance_id"] = zone->GetInstanceID();
|
row["instance_id"] = zone->GetInstanceID();
|
||||||
@@ -109,9 +111,17 @@ void callGetDatabaseSchema(Json::Value &response)
|
|||||||
response.append(schema);
|
response.append(schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
void callGetClientList(Json::Value &response)
|
void callGetClientList(Json::Value &response, const std::vector<std::string> &args)
|
||||||
{
|
{
|
||||||
client_list.GetClientList(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
void getReloadTypes(Json::Value &response)
|
void getReloadTypes(Json::Value &response)
|
||||||
@@ -125,6 +135,12 @@ 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)
|
void EQEmuApiWorldDataService::reload(Json::Value &r, const std::vector<std::string> &args)
|
||||||
{
|
{
|
||||||
std::vector<std::string> commands{};
|
std::vector<std::string> commands{};
|
||||||
@@ -172,7 +188,7 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
|
|||||||
callGetDatabaseSchema(r);
|
callGetDatabaseSchema(r);
|
||||||
}
|
}
|
||||||
if (m == "get_client_list") {
|
if (m == "get_client_list") {
|
||||||
callGetClientList(r);
|
callGetClientList(r, args);
|
||||||
}
|
}
|
||||||
if (m == "get_reload_types") {
|
if (m == "get_reload_types") {
|
||||||
getReloadTypes(r);
|
getReloadTypes(r);
|
||||||
@@ -183,6 +199,9 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
|
|||||||
if (m == "get_guild_details") {
|
if (m == "get_guild_details") {
|
||||||
callGetGuildDetails(r, args);
|
callGetGuildDetails(r, args);
|
||||||
}
|
}
|
||||||
|
if (m == "get_server_counts") {
|
||||||
|
getServerCounts(r, args);
|
||||||
|
}
|
||||||
if (m == "lock_status") {
|
if (m == "lock_status") {
|
||||||
r["locked"] = WorldConfig::get()->Locked;
|
r["locked"] = WorldConfig::get()->Locked;
|
||||||
}
|
}
|
||||||
@@ -190,7 +209,6 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
|
|||||||
|
|
||||||
void EQEmuApiWorldDataService::callGetGuildDetails(Json::Value &response, const std::vector<std::string> &args)
|
void EQEmuApiWorldDataService::callGetGuildDetails(Json::Value &response, const std::vector<std::string> &args)
|
||||||
{
|
{
|
||||||
|
|
||||||
std::string command = !args[1].empty() ? args[1] : "";
|
std::string command = !args[1].empty() ? args[1] : "";
|
||||||
if (command.empty()) {
|
if (command.empty()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ void LoginServer::ProcessLSFatalError(uint16_t opcode, EQ::Net::Packet &p)
|
|||||||
if (error.find("Worldserver Account / Password INVALID") != std::string::npos) {
|
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. ";
|
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) {
|
if (fmt::format("{}", m_loginserver_address).find("login.eqemulator.net") != std::string::npos) {
|
||||||
reason += "For Legacy EQEmulator connections, you need to register your server @ http://www.eqemulator.org/account/?LS";
|
reason += "For Legacy EQEmulator connections, you need to register your server @ https://www.eqemulator.org/index.php?pageid=ws_mgmt";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-5
@@ -381,11 +381,19 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
Timer player_event_process_timer(1000);
|
|
||||||
if (player_event_logs.LoadDatabaseConnection()) {
|
if (player_event_logs.LoadDatabaseConnection()) {
|
||||||
player_event_logs.Init();
|
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) {
|
auto loop_fn = [&](EQ::Timer* t) {
|
||||||
Timer::SetCurrentTime();
|
Timer::SetCurrentTime();
|
||||||
|
|
||||||
@@ -448,10 +456,6 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player_event_process_timer.Check()) {
|
|
||||||
std::jthread event_thread(&PlayerEventLogs::Process, &player_event_logs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PurgeInstanceTimer.Check()) {
|
if (PurgeInstanceTimer.Check()) {
|
||||||
database.PurgeExpiredInstances();
|
database.PurgeExpiredInstances();
|
||||||
database.PurgeAllDeletedDataBuckets();
|
database.PurgeAllDeletedDataBuckets();
|
||||||
@@ -502,6 +506,8 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
EQ::EventLoop::Get().Run();
|
EQ::EventLoop::Get().Run();
|
||||||
|
|
||||||
|
event_log_processor.request_stop();
|
||||||
|
|
||||||
LogInfo("World main loop completed");
|
LogInfo("World main loop completed");
|
||||||
LogInfo("Shutting down zone connections (if any)");
|
LogInfo("Shutting down zone connections (if any)");
|
||||||
zoneserver_list.KillAll();
|
zoneserver_list.KillAll();
|
||||||
|
|||||||
+20
-15
@@ -50,7 +50,7 @@ void WorldGuildManager::SendGuildRefresh(uint32 guild_id, bool name, bool motd,
|
|||||||
s->motd_change = motd;
|
s->motd_change = motd;
|
||||||
s->rank_change = rank;
|
s->rank_change = rank;
|
||||||
s->relation_change = relation;
|
s->relation_change = relation;
|
||||||
zoneserver_list.SendPacket(pack);
|
zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
|
||||||
safe_delete(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->guild_id = guild_id;
|
||||||
s->old_guild_id = old_guild_id;
|
s->old_guild_id = old_guild_id;
|
||||||
s->char_id = charid;
|
s->char_id = charid;
|
||||||
zoneserver_list.SendPacket(pack);
|
zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
|
||||||
safe_delete(pack);
|
safe_delete(pack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ void WorldGuildManager::SendGuildDelete(uint32 guild_id) {
|
|||||||
auto pack = new ServerPacket(ServerOP_DeleteGuild, sizeof(ServerGuildID_Struct));
|
auto pack = new ServerPacket(ServerOP_DeleteGuild, sizeof(ServerGuildID_Struct));
|
||||||
ServerGuildID_Struct *s = (ServerGuildID_Struct *) pack->pBuffer;
|
ServerGuildID_Struct *s = (ServerGuildID_Struct *) pack->pBuffer;
|
||||||
s->guild_id = guild_id;
|
s->guild_id = guild_id;
|
||||||
zoneserver_list.SendPacket(pack);
|
zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
|
||||||
safe_delete(pack);
|
safe_delete(pack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,15 +85,14 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
|||||||
ServerGuildRefresh_Struct *s = (ServerGuildRefresh_Struct *) pack->pBuffer;
|
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);
|
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.
|
//preform a local refresh.
|
||||||
if(!RefreshGuild(s->guild_id)) {
|
if(!RefreshGuild(s->guild_id)) {
|
||||||
LogGuilds("Unable to preform local refresh on guild [{}]", s->guild_id);
|
BaseGuildManager::RefreshGuild(s->guild_id);
|
||||||
//can we do anything?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//broadcast this packet to all zones.
|
||||||
|
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +107,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
|||||||
|
|
||||||
//broadcast this update to any zone with a member in this guild.
|
//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.
|
//because im sick of this not working, sending it to all zones, just spends a bit more bandwidth.
|
||||||
zoneserver_list.SendPacket(pack);
|
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -147,7 +146,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
|||||||
auto s = (ServerGuildID_Struct *)pack->pBuffer;
|
auto s = (ServerGuildID_Struct *)pack->pBuffer;
|
||||||
RefreshGuild(s->guild_id);
|
RefreshGuild(s->guild_id);
|
||||||
|
|
||||||
zoneserver_list.SendPacket(pack);
|
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ServerOP_GuildPermissionUpdate:
|
case ServerOP_GuildPermissionUpdate:
|
||||||
@@ -179,7 +178,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
|||||||
sg->function_value
|
sg->function_value
|
||||||
);
|
);
|
||||||
|
|
||||||
zoneserver_list.SendPacketToBootedZones(pack);
|
zoneserver_list.SendPacketToZonesWithGuild(sg->guild_id, pack);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
LogError("World Received ServerOP_GuildPermissionUpdate for guild [{}] function id {} with value of {} but guild could not be found.",
|
LogError("World Received ServerOP_GuildPermissionUpdate for guild [{}] function id {} with value of {} but guild could not be found.",
|
||||||
@@ -213,7 +212,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
|||||||
rnc->rank,
|
rnc->rank,
|
||||||
rnc->rank_name
|
rnc->rank_name
|
||||||
);
|
);
|
||||||
zoneserver_list.SendPacketToBootedZones(pack);
|
zoneserver_list.SendPacketToZonesWithGuild(rnc->guild_id, pack);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
LogError("World Received ServerOP_GuildRankNameChange from zone for guild [{}] rank id {} with new name of {} but could not find guild.",
|
LogError("World Received ServerOP_GuildRankNameChange from zone for guild [{}] rank id {} with new name of {} but could not find guild.",
|
||||||
@@ -230,10 +229,10 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
|||||||
case ServerOP_GuildChannel:
|
case ServerOP_GuildChannel:
|
||||||
case ServerOP_GuildURL:
|
case ServerOP_GuildURL:
|
||||||
case ServerOP_GuildMemberRemove:
|
case ServerOP_GuildMemberRemove:
|
||||||
case ServerOP_GuildSendGuildList:
|
|
||||||
case ServerOP_GuildMembersList:
|
case ServerOP_GuildMembersList:
|
||||||
{
|
{
|
||||||
zoneserver_list.SendPacketToBootedZones(pack);
|
auto in = (ServerOP_GuildMessage_Struct *) pack->pBuffer;
|
||||||
|
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, pack);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ServerOP_GuildMemberAdd:
|
case ServerOP_GuildMemberAdd:
|
||||||
@@ -244,9 +243,15 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
|||||||
BaseGuildManager::RefreshGuild(in->guild_id);
|
BaseGuildManager::RefreshGuild(in->guild_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, pack);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ServerOP_GuildSendGuildList: {
|
||||||
|
auto in = (ServerOP_GuildMessage_Struct *) pack->pBuffer;
|
||||||
zoneserver_list.SendPacketToBootedZones(pack);
|
zoneserver_list.SendPacketToBootedZones(pack);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
LogGuilds("Unknown packet {:#04x} received from zone??", pack->opcode);
|
LogGuilds("Unknown packet {:#04x} received from zone??", pack->opcode);
|
||||||
break;
|
break;
|
||||||
@@ -451,6 +456,6 @@ void WorldGuildManager::SendGuildTributeFavorAndTimer(uint32 guild_id, uint32 fa
|
|||||||
data->tribute_timer = time;
|
data->tribute_timer = time;
|
||||||
data->trophy_timer = 0;
|
data->trophy_timer = 0;
|
||||||
|
|
||||||
zoneserver_list.SendPacketToBootedZones(sp);
|
zoneserver_list.SendPacketToZonesWithGuild(guild_id, sp);
|
||||||
safe_delete(sp)
|
safe_delete(sp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "../common/zone_store.h"
|
#include "../common/zone_store.h"
|
||||||
#include "../common/path_manager.h"
|
#include "../common/path_manager.h"
|
||||||
#include "../common/database/database_update.h"
|
#include "../common/database/database_update.h"
|
||||||
|
#include "../common/repositories/zone_state_spawns_repository.h"
|
||||||
|
|
||||||
extern ZSList zoneserver_list;
|
extern ZSList zoneserver_list;
|
||||||
extern WorldConfig Config;
|
extern WorldConfig Config;
|
||||||
@@ -412,6 +413,11 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv)
|
|||||||
LogInfo("Cleaning up instance corpses");
|
LogInfo("Cleaning up instance corpses");
|
||||||
database.CleanupInstanceCorpses();
|
database.CleanupInstanceCorpses();
|
||||||
|
|
||||||
|
if (RuleB(Zone, StateSavingOnShutdown)) {
|
||||||
|
ZoneStateSpawnsRepository::PurgeInvalidZoneStates(database);
|
||||||
|
ZoneStateSpawnsRepository::PurgeOldZoneStates(database);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+237
-273
@@ -29,6 +29,10 @@
|
|||||||
#include "../common/repositories/inventory_repository.h"
|
#include "../common/repositories/inventory_repository.h"
|
||||||
#include "../common/repositories/criteria/content_filter_criteria.h"
|
#include "../common/repositories/criteria/content_filter_criteria.h"
|
||||||
#include "../common/zone_store.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 database;
|
||||||
WorldDatabase content_db;
|
WorldDatabase content_db;
|
||||||
@@ -50,187 +54,177 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o
|
|||||||
character_limit = 8;
|
character_limit = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string character_list_query = fmt::format(
|
auto characters = CharacterDataRepository::GetWhere(
|
||||||
SQL(
|
database,
|
||||||
SELECT
|
fmt::format(
|
||||||
`id`,
|
"`account_id` = {} AND `deleted_at` IS NULL ORDER BY `name` LIMIT {}",
|
||||||
`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,
|
account_id,
|
||||||
character_limit
|
character_limit
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
auto results = database.QueryDatabase(character_list_query);
|
size_t character_count = characters.size();
|
||||||
size_t character_count = results.RowCount();
|
if (characters.empty()) {
|
||||||
if (character_count == 0) {
|
|
||||||
*out_app = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct));
|
*out_app = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct));
|
||||||
CharacterSelect_Struct *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer;
|
auto *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer;
|
||||||
cs->CharCount = 0;
|
cs->CharCount = 0;
|
||||||
cs->TotalChars = character_limit;
|
cs->TotalChars = character_limit;
|
||||||
return;
|
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);
|
size_t packet_size = sizeof(CharacterSelect_Struct) + (sizeof(CharacterSelectEntry_Struct) * character_count);
|
||||||
*out_app = new EQApplicationPacket(OP_SendCharInfo, packet_size);
|
*out_app = new EQApplicationPacket(OP_SendCharInfo, packet_size);
|
||||||
|
|
||||||
unsigned char *buff_ptr = (*out_app)->pBuffer;
|
unsigned char *buff_ptr = (*out_app)->pBuffer;
|
||||||
CharacterSelect_Struct *cs = (CharacterSelect_Struct *) buff_ptr;
|
auto *cs = (CharacterSelect_Struct *) buff_ptr;
|
||||||
|
|
||||||
cs->CharCount = character_count;
|
cs->CharCount = character_count;
|
||||||
cs->TotalChars = character_limit;
|
cs->TotalChars = character_limit;
|
||||||
|
|
||||||
buff_ptr += sizeof(CharacterSelect_Struct);
|
buff_ptr += sizeof(CharacterSelect_Struct);
|
||||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
for (auto &e: characters) {
|
||||||
CharacterSelectEntry_Struct *p_character_select_entry_struct = (CharacterSelectEntry_Struct *) buff_ptr;
|
auto *cse = (CharacterSelectEntry_Struct *) buff_ptr;
|
||||||
PlayerProfile_Struct pp;
|
PlayerProfile_Struct pp;
|
||||||
EQ::InventoryProfile inventory_profile;
|
EQ::InventoryProfile inv;
|
||||||
|
|
||||||
pp.SetPlayerProfileVersion(EQ::versions::ConvertClientVersionToMobVersion(client_version));
|
pp.SetPlayerProfileVersion(EQ::versions::ConvertClientVersionToMobVersion(client_version));
|
||||||
inventory_profile.SetInventoryVersion(client_version);
|
inv.SetInventoryVersion(client_version);
|
||||||
inventory_profile.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support
|
inv.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support
|
||||||
|
|
||||||
uint32 character_id = Strings::ToUnsignedInt(row[0]);
|
uint32 character_id = e.id;
|
||||||
uint8 has_home = 0;
|
uint8 has_home = 0;
|
||||||
uint8 has_bind = 0;
|
uint8 has_bind = 0;
|
||||||
|
|
||||||
memset(&pp, 0, sizeof(PlayerProfile_Struct));
|
memset(&pp, 0, sizeof(PlayerProfile_Struct));
|
||||||
memset(p_character_select_entry_struct->Name, 0, sizeof(p_character_select_entry_struct->Name));
|
memset(cse->Name, 0, sizeof(cse->Name));
|
||||||
strcpy(p_character_select_entry_struct->Name, row[1]);
|
strcpy(cse->Name, e.name.c_str());
|
||||||
p_character_select_entry_struct->Class = (uint8) Strings::ToUnsignedInt(row[4]);
|
cse->Class = e.class_;
|
||||||
p_character_select_entry_struct->Race = (uint32) Strings::ToUnsignedInt(row[3]);
|
cse->Race = e.race;
|
||||||
p_character_select_entry_struct->Level = (uint8) Strings::ToUnsignedInt(row[5]);
|
cse->Level = e.level;
|
||||||
p_character_select_entry_struct->ShroudClass = p_character_select_entry_struct->Class;
|
cse->ShroudClass = cse->Class;
|
||||||
p_character_select_entry_struct->ShroudRace = p_character_select_entry_struct->Race;
|
cse->ShroudRace = cse->Race;
|
||||||
p_character_select_entry_struct->Zone = (uint16) Strings::ToUnsignedInt(row[19]);
|
cse->Zone = e.zone_id;
|
||||||
p_character_select_entry_struct->Instance = 0;
|
cse->Instance = 0;
|
||||||
p_character_select_entry_struct->Gender = (uint8) Strings::ToUnsignedInt(row[2]);
|
cse->Gender = e.gender;
|
||||||
p_character_select_entry_struct->Face = (uint8) Strings::ToUnsignedInt(row[15]);
|
cse->Face = e.face;
|
||||||
|
|
||||||
for (uint32 material_slot = 0; material_slot < EQ::textures::materialCount; material_slot++) {
|
for (auto &s: cse->Equip) {
|
||||||
p_character_select_entry_struct->Equip[material_slot].Material = 0;
|
s.Material = 0;
|
||||||
p_character_select_entry_struct->Equip[material_slot].Unknown1 = 0;
|
s.Unknown1 = 0;
|
||||||
p_character_select_entry_struct->Equip[material_slot].EliteModel = 0;
|
s.EliteModel = 0;
|
||||||
p_character_select_entry_struct->Equip[material_slot].HerosForgeModel = 0;
|
s.HerosForgeModel = 0;
|
||||||
p_character_select_entry_struct->Equip[material_slot].Unknown2 = 0;
|
s.Unknown2 = 0;
|
||||||
p_character_select_entry_struct->Equip[material_slot].Color = 0;
|
s.Color = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
p_character_select_entry_struct->Unknown15 = 0xFF;
|
cse->Unknown15 = 0xFF;
|
||||||
p_character_select_entry_struct->Unknown19 = 0xFF;
|
cse->Unknown19 = 0xFF;
|
||||||
p_character_select_entry_struct->DrakkinTattoo = (uint32) Strings::ToInt(row[17]);
|
cse->DrakkinTattoo = e.drakkin_tattoo;
|
||||||
p_character_select_entry_struct->DrakkinDetails = (uint32) Strings::ToInt(row[18]);
|
cse->DrakkinDetails = e.drakkin_details;
|
||||||
p_character_select_entry_struct->Deity = (uint32) Strings::ToInt(row[6]);
|
cse->Deity = e.deity;
|
||||||
p_character_select_entry_struct->PrimaryIDFile = 0; // Processed Below
|
cse->PrimaryIDFile = 0; // Processed Below
|
||||||
p_character_select_entry_struct->SecondaryIDFile = 0; // Processed Below
|
cse->SecondaryIDFile = 0; // Processed Below
|
||||||
p_character_select_entry_struct->HairColor = (uint8) Strings::ToInt(row[9]);
|
cse->HairColor = e.hair_color;
|
||||||
p_character_select_entry_struct->BeardColor = (uint8) Strings::ToInt(row[10]);
|
cse->BeardColor = e.beard_color;
|
||||||
p_character_select_entry_struct->EyeColor1 = (uint8) Strings::ToInt(row[11]);
|
cse->EyeColor1 = e.eye_color_1;
|
||||||
p_character_select_entry_struct->EyeColor2 = (uint8) Strings::ToInt(row[12]);
|
cse->EyeColor2 = e.eye_color_2;
|
||||||
p_character_select_entry_struct->HairStyle = (uint8) Strings::ToInt(row[13]);
|
cse->HairStyle = e.hair_style;
|
||||||
p_character_select_entry_struct->Beard = (uint8) Strings::ToInt(row[14]);
|
cse->Beard = e.beard;
|
||||||
p_character_select_entry_struct->GoHome = 0; // Processed Below
|
cse->GoHome = 0; // Processed Below
|
||||||
p_character_select_entry_struct->Tutorial = 0; // Processed Below
|
cse->Tutorial = 0; // Processed Below
|
||||||
p_character_select_entry_struct->DrakkinHeritage = (uint32) Strings::ToInt(row[16]);
|
cse->DrakkinHeritage = e.drakkin_heritage;
|
||||||
p_character_select_entry_struct->Unknown1 = 0;
|
cse->Unknown1 = 0;
|
||||||
p_character_select_entry_struct->Enabled = 1;
|
cse->Enabled = 1;
|
||||||
p_character_select_entry_struct->LastLogin = (uint32) Strings::ToInt(row[7]); // RoF2 value: 1212696584
|
cse->LastLogin = e.last_login; // RoF2 value: 1212696584
|
||||||
p_character_select_entry_struct->Unknown2 = 0;
|
cse->Unknown2 = 0;
|
||||||
|
|
||||||
if (RuleB(World, EnableReturnHomeButton)) {
|
if (RuleB(World, EnableReturnHomeButton)) {
|
||||||
int now = time(nullptr);
|
int now = time(nullptr);
|
||||||
if ((now - Strings::ToInt(row[7])) >= RuleI(World, MinOfflineTimeToReturnHome))
|
if (now - e.last_login >= RuleI(World, MinOfflineTimeToReturnHome)) {
|
||||||
p_character_select_entry_struct->GoHome = 1;
|
cse->GoHome = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RuleB(World, EnableTutorialButton) && (p_character_select_entry_struct->Level <= RuleI(World, MaxLevelForTutorial)))
|
if (RuleB(World, EnableTutorialButton) && (cse->Level <= RuleI(World, MaxLevelForTutorial))) {
|
||||||
p_character_select_entry_struct->Tutorial = 1;
|
cse->Tutorial = 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
// binds
|
||||||
* Bind
|
int bind_count = 0;
|
||||||
*/
|
for (auto &bind : character_binds) {
|
||||||
character_list_query = fmt::format(
|
if (bind.id != e.id) {
|
||||||
SQL(
|
continue;
|
||||||
SELECT
|
}
|
||||||
`zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`
|
|
||||||
FROM
|
if (bind.slot == 4) {
|
||||||
`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;
|
has_home = 1;
|
||||||
// If our bind count is less than 5, we need to actually make use of this data so lets parse it
|
pp.binds[4].zone_id = bind.zone_id;
|
||||||
if (bind_count < 5) {
|
pp.binds[4].instance_id = bind.instance_id;
|
||||||
pp.binds[4].zone_id = Strings::ToInt(row_b[0]);
|
pp.binds[4].x = bind.x;
|
||||||
pp.binds[4].instance_id = Strings::ToInt(row_b[1]);
|
pp.binds[4].y = bind.y;
|
||||||
pp.binds[4].x = Strings::ToFloat(row_b[2]);
|
pp.binds[4].z = bind.z;
|
||||||
pp.binds[4].y = Strings::ToFloat(row_b[3]);
|
pp.binds[4].heading = bind.heading;
|
||||||
pp.binds[4].z = Strings::ToFloat(row_b[4]);
|
|
||||||
pp.binds[4].heading = Strings::ToFloat(row_b[5]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row_b[6] && Strings::ToInt(row_b[6]) == 0)
|
if (bind.slot == 0) {
|
||||||
has_bind = 1;
|
has_bind = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (has_home == 0 || has_bind == 0) {
|
bind_count++;
|
||||||
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 (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()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
auto z = GetZone(pp.binds[4].zone_id);
|
||||||
if (z) {
|
if (z) {
|
||||||
pp.binds[4].x = z->safe_x;
|
pp.binds[4].x = z->safe_x;
|
||||||
@@ -238,168 +232,151 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o
|
|||||||
pp.binds[4].z = z->safe_z;
|
pp.binds[4].z = z->safe_z;
|
||||||
pp.binds[4].heading = z->safe_heading;
|
pp.binds[4].heading = z->safe_heading;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
/* Otherwise, use the zone and coordinates given */
|
pp.binds[4].zone_id = start_zones[0].zone_id;
|
||||||
else {
|
pp.binds[4].x = start_zones[0].x;
|
||||||
pp.binds[4].zone_id = (uint32) Strings::ToInt(row_d[0]);
|
pp.binds[4].y = start_zones[0].y;
|
||||||
float x = Strings::ToFloat(row_d[2]);
|
pp.binds[4].z = start_zones[0].z;
|
||||||
float y = Strings::ToFloat(row_d[3]);
|
pp.binds[4].heading = start_zones[0].heading;
|
||||||
float z = Strings::ToFloat(row_d[4]);
|
|
||||||
float heading = Strings::ToFloat(row_d[5]);
|
if (pp.binds[4].x == 0 && pp.binds[4].y == 0 && pp.binds[4].z == 0 && pp.binds[4].heading == 0) {
|
||||||
if (x == 0 && y == 0 && z == 0 && heading == 0) {
|
|
||||||
auto zone = GetZone(pp.binds[4].zone_id);
|
auto zone = GetZone(pp.binds[4].zone_id);
|
||||||
if (zone) {
|
if (zone) {
|
||||||
x = zone->safe_x;
|
pp.binds[4].x = zone->safe_x;
|
||||||
y = zone->safe_y;
|
pp.binds[4].y = zone->safe_y;
|
||||||
z = zone->safe_z;
|
pp.binds[4].z = zone->safe_z;
|
||||||
heading = zone->safe_heading;
|
pp.binds[4].heading = zone->safe_heading;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pp.binds[4].x = x;
|
|
||||||
pp.binds[4].y = y;
|
|
||||||
pp.binds[4].z = z;
|
|
||||||
pp.binds[4].heading = heading;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
LogError("No start zone found for class [{}] deity [{}] race [{}]", cse->Class, cse->Deity, cse->Race);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pp.binds[0] = pp.binds[4];
|
pp.binds[0] = pp.binds[4];
|
||||||
/* If no home bind set, set it */
|
|
||||||
|
// If we don't have home set, set it
|
||||||
if (has_home == 0) {
|
if (has_home == 0) {
|
||||||
std::string query = fmt::format(
|
auto bind = CharacterBindRepository::NewEntity();
|
||||||
SQL(
|
bind.id = character_id;
|
||||||
REPLACE INTO
|
bind.zone_id = pp.binds[4].zone_id;
|
||||||
`character_bind`
|
bind.instance_id = 0;
|
||||||
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`)
|
bind.x = pp.binds[4].x;
|
||||||
VALUES ({}, {}, {}, {}, {}, {}, {}, {})
|
bind.y = pp.binds[4].y;
|
||||||
),
|
bind.z = pp.binds[4].z;
|
||||||
character_id,
|
bind.heading = pp.binds[4].heading;
|
||||||
pp.binds[4].zone_id,
|
bind.slot = 4;
|
||||||
0,
|
CharacterBindRepository::ReplaceOne(*this, bind);
|
||||||
pp.binds[4].x,
|
|
||||||
pp.binds[4].y,
|
|
||||||
pp.binds[4].z,
|
|
||||||
pp.binds[4].heading,
|
|
||||||
4
|
|
||||||
);
|
|
||||||
auto results_bset = QueryDatabase(query);
|
|
||||||
}
|
}
|
||||||
/* If no regular bind set, set it */
|
|
||||||
|
// If we don't have regular bind set, set it
|
||||||
if (has_bind == 0) {
|
if (has_bind == 0) {
|
||||||
std::string query = fmt::format(
|
auto bind = CharacterBindRepository::NewEntity();
|
||||||
SQL(
|
bind.id = character_id;
|
||||||
REPLACE INTO
|
bind.zone_id = pp.binds[0].zone_id;
|
||||||
`character_bind`
|
bind.instance_id = 0;
|
||||||
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`)
|
bind.x = pp.binds[0].x;
|
||||||
VALUES ({}, {}, {}, {}, {}, {}, {}, {})
|
bind.y = pp.binds[0].y;
|
||||||
),
|
bind.z = pp.binds[0].z;
|
||||||
character_id,
|
bind.heading = pp.binds[0].heading;
|
||||||
pp.binds[0].zone_id,
|
bind.slot = 0;
|
||||||
0,
|
CharacterBindRepository::ReplaceOne(*this, bind);
|
||||||
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) {
|
if (bind_count < 5) {
|
||||||
// we know that home and main bind must be valid here, so we don't check those
|
// 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.
|
// 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++) {
|
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;
|
continue;
|
||||||
|
|
||||||
std::string query = fmt::format(
|
|
||||||
SQL(
|
|
||||||
REPLACE INTO
|
|
||||||
`character_bind`
|
|
||||||
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`)
|
|
||||||
VALUES ({}, {}, {}, {}, {}, {}, {}, {})
|
|
||||||
),
|
|
||||||
character_id,
|
|
||||||
pp.binds[4].zone_id,
|
|
||||||
0,
|
|
||||||
pp.binds[4].x,
|
|
||||||
pp.binds[4].y,
|
|
||||||
pp.binds[4].z,
|
|
||||||
pp.binds[4].heading,
|
|
||||||
i
|
|
||||||
);
|
|
||||||
auto results_bset = QueryDatabase(query);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
character_list_query = fmt::format(
|
auto bind = CharacterBindRepository::NewEntity();
|
||||||
SQL(
|
|
||||||
SELECT
|
bind.slot = i;
|
||||||
`slot`, `red`, `green`, `blue`, `use_tint`, `color`
|
bind.id = character_id;
|
||||||
FROM
|
bind.zone_id = pp.binds[4].zone_id;
|
||||||
`character_material`
|
bind.instance_id = 0;
|
||||||
WHERE
|
bind.x = pp.binds[4].x;
|
||||||
`id` = {}
|
bind.y = pp.binds[4].y;
|
||||||
),
|
bind.z = pp.binds[4].z;
|
||||||
character_id
|
bind.heading = pp.binds[4].heading;
|
||||||
);
|
binds.emplace_back(bind);
|
||||||
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(account_id, p_character_select_entry_struct->Name, &inventory_profile)) {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetCharSelInventory(inventories, e, &inv)) {
|
||||||
const EQ::ItemData *item = nullptr;
|
const EQ::ItemData *item = nullptr;
|
||||||
const EQ::ItemInstance *inst = nullptr;
|
const EQ::ItemInstance *inst = nullptr;
|
||||||
int16 inventory_slot = 0;
|
int16 inventory_slot = 0;
|
||||||
|
|
||||||
for (uint32 matslot = EQ::textures::textureBegin; matslot < EQ::textures::materialCount; matslot++) {
|
for (uint32 matslot = EQ::textures::textureBegin; matslot < EQ::textures::materialCount; matslot++) {
|
||||||
inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(matslot);
|
inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(matslot);
|
||||||
if (inventory_slot == INVALID_INDEX) { continue; }
|
if (inventory_slot == INVALID_INDEX) { continue; }
|
||||||
inst = inventory_profile.GetItem(inventory_slot);
|
inst = inv.GetItem(inventory_slot);
|
||||||
if (inst == nullptr)
|
if (inst == nullptr) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
item = inst->GetItem();
|
item = inst->GetItem();
|
||||||
if (item == nullptr)
|
if (item == nullptr) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (matslot > 6) {
|
if (matslot > 6) {
|
||||||
uint32 item_id_file = 0;
|
uint32 item_id_file = 0;
|
||||||
// Weapon Models
|
// Weapon Models
|
||||||
if (inst->GetOrnamentationIDFile() != 0) {
|
if (inst->GetOrnamentationIDFile() != 0) {
|
||||||
item_id_file = inst->GetOrnamentationIDFile();
|
item_id_file = inst->GetOrnamentationIDFile();
|
||||||
p_character_select_entry_struct->Equip[matslot].Material = item_id_file;
|
cse->Equip[matslot].Material = item_id_file;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (strlen(item->IDFile) > 2) {
|
if (strlen(item->IDFile) > 2) {
|
||||||
item_id_file = Strings::ToInt(&item->IDFile[2]);
|
item_id_file = Strings::ToInt(&item->IDFile[2]);
|
||||||
p_character_select_entry_struct->Equip[matslot].Material = item_id_file;
|
cse->Equip[matslot].Material = item_id_file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matslot == EQ::textures::weaponPrimary) {
|
if (matslot == EQ::textures::weaponPrimary) {
|
||||||
p_character_select_entry_struct->PrimaryIDFile = item_id_file;
|
cse->PrimaryIDFile = item_id_file;
|
||||||
} else {
|
|
||||||
p_character_select_entry_struct->SecondaryIDFile = item_id_file;
|
|
||||||
}
|
}
|
||||||
} else {
|
else {
|
||||||
|
cse->SecondaryIDFile = item_id_file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
// Armor Materials/Models
|
// Armor Materials/Models
|
||||||
uint32 color = (
|
uint32 color = (
|
||||||
pp.item_tint.Slot[matslot].UseTint ?
|
pp.item_tint.Slot[matslot].UseTint ?
|
||||||
pp.item_tint.Slot[matslot].Color :
|
pp.item_tint.Slot[matslot].Color :
|
||||||
inst->GetColor()
|
inst->GetColor()
|
||||||
);
|
);
|
||||||
p_character_select_entry_struct->Equip[matslot].Material = item->Material;
|
cse->Equip[matslot].Material = item->Material;
|
||||||
p_character_select_entry_struct->Equip[matslot].EliteModel = item->EliteMaterial;
|
cse->Equip[matslot].EliteModel = item->EliteMaterial;
|
||||||
p_character_select_entry_struct->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot);
|
cse->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot);
|
||||||
p_character_select_entry_struct->Equip[matslot].Color = color;
|
cse->Equip[matslot].Color = color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
printf("Error loading inventory for %s\n", p_character_select_entry_struct->Name);
|
else {
|
||||||
|
printf("Error loading inventory for %s\n", cse->Name);
|
||||||
}
|
}
|
||||||
buff_ptr += sizeof(CharacterSelectEntry_Struct);
|
buff_ptr += sizeof(CharacterSelectEntry_Struct);
|
||||||
}
|
}
|
||||||
@@ -849,34 +826,21 @@ bool WorldDatabase::LoadCharacterCreateCombos()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is a slightly modified version of SharedDatabase::GetInventory(...) for character select use-only
|
bool WorldDatabase::GetCharSelInventory(
|
||||||
bool WorldDatabase::GetCharSelInventory(uint32 account_id, char *name, EQ::InventoryProfile *inv)
|
const std::vector<InventoryRepository::Inventory> &inventories,
|
||||||
{
|
const CharacterDataRepository::CharacterData &character,
|
||||||
if (!account_id || !name || !inv) {
|
EQ::InventoryProfile *inv
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (inventories.empty() || !character.id || !inv) {
|
||||||
if (l.empty()) {
|
return false;
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
for (const auto& e : inventories) {
|
||||||
|
if (e.character_id != character.id) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& e : l) {
|
|
||||||
switch (e.slot_id) {
|
switch (e.slot_id) {
|
||||||
case EQ::invslot::slotFace:
|
case EQ::invslot::slotFace:
|
||||||
case EQ::invslot::slotEar2:
|
case EQ::invslot::slotEar2:
|
||||||
|
|||||||
+7
-1
@@ -20,6 +20,8 @@
|
|||||||
|
|
||||||
#include "../common/shareddb.h"
|
#include "../common/shareddb.h"
|
||||||
#include "../common/eq_packet.h"
|
#include "../common/eq_packet.h"
|
||||||
|
#include "../common/repositories/inventory_repository.h"
|
||||||
|
#include "../common/repositories/character_data_repository.h"
|
||||||
|
|
||||||
struct PlayerProfile_Struct;
|
struct PlayerProfile_Struct;
|
||||||
struct CharCreate_Struct;
|
struct CharCreate_Struct;
|
||||||
@@ -43,7 +45,11 @@ private:
|
|||||||
void SetTitaniumDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc);
|
void SetTitaniumDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc);
|
||||||
void SetSoFDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc);
|
void SetSoFDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc);
|
||||||
|
|
||||||
bool GetCharSelInventory(uint32 account_id, char* name, EQ::InventoryProfile* inv);
|
bool GetCharSelInventory(
|
||||||
|
const std::vector<InventoryRepository::Inventory> &inventories,
|
||||||
|
const CharacterDataRepository::CharacterData &character,
|
||||||
|
EQ::InventoryProfile *inv
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern WorldDatabase database;
|
extern WorldDatabase database;
|
||||||
|
|||||||
+61
-6
@@ -36,11 +36,13 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include "shared_task_manager.h"
|
#include "shared_task_manager.h"
|
||||||
#include "dynamic_zone_manager.h"
|
#include "dynamic_zone_manager.h"
|
||||||
#include "ucs.h"
|
#include "ucs.h"
|
||||||
|
#include "clientlist.h"
|
||||||
|
|
||||||
extern uint32 numzones;
|
extern uint32 numzones;
|
||||||
extern EQ::Random emu_random;
|
extern EQ::Random emu_random;
|
||||||
extern WebInterfaceList web_interface;
|
extern WebInterfaceList web_interface;
|
||||||
extern SharedTaskManager shared_task_manager;
|
extern SharedTaskManager shared_task_manager;
|
||||||
|
extern ClientList client_list;
|
||||||
volatile bool UCSServerAvailable_ = false;
|
volatile bool UCSServerAvailable_ = false;
|
||||||
void CatchSignal(int sig_num);
|
void CatchSignal(int sig_num);
|
||||||
|
|
||||||
@@ -515,9 +517,17 @@ void ZSList::SendEmoteMessage(const char* to, uint32 to_guilddbid, int16 to_mins
|
|||||||
SendEmoteMessageRaw(to, to_guilddbid, to_minstatus, type, buffer);
|
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) {
|
void ZSList::SendEmoteMessageRaw(
|
||||||
if (!message)
|
const char *to,
|
||||||
|
uint32 to_guilddbid,
|
||||||
|
int16 to_minstatus,
|
||||||
|
uint32 type,
|
||||||
|
const char *message
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!message) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
auto pack = new ServerPacket;
|
auto pack = new ServerPacket;
|
||||||
|
|
||||||
pack->opcode = ServerOP_EmoteMessage;
|
pack->opcode = ServerOP_EmoteMessage;
|
||||||
@@ -538,20 +548,35 @@ void ZSList::SendEmoteMessageRaw(const char* to, uint32 to_guilddbid, int16 to_m
|
|||||||
sem->type = type;
|
sem->type = type;
|
||||||
strcpy(&sem->message[0], message);
|
strcpy(&sem->message[0], message);
|
||||||
char tempto[64] = {0};
|
char tempto[64] = {0};
|
||||||
if (to)
|
if (to) {
|
||||||
strn0cpy(tempto, to, 64);
|
strn0cpy(tempto, to, 64);
|
||||||
|
}
|
||||||
|
|
||||||
if (tempto[0] == 0) {
|
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 {
|
else {
|
||||||
ZoneServer *zs = FindByName(to);
|
ZoneServer *zs = FindByName(to);
|
||||||
|
if (zs) {
|
||||||
if (zs != 0)
|
|
||||||
zs->SendPacket(pack);
|
zs->SendPacket(pack);
|
||||||
else
|
}
|
||||||
|
else if (to_guilddbid > 0) {
|
||||||
|
SendPacketToZonesWithGuild(to_guilddbid, pack);
|
||||||
|
}
|
||||||
|
else if (to_minstatus > 0) {
|
||||||
|
SendPacketToZonesWithGMs(pack);
|
||||||
|
}
|
||||||
|
else {
|
||||||
SendPacket(pack);
|
SendPacket(pack);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
delete pack;
|
delete pack;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -871,6 +896,34 @@ bool ZSList::SendPacketToBootedZones(ServerPacket* pack)
|
|||||||
return true;
|
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)
|
void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
|
||||||
{
|
{
|
||||||
static auto pack = ServerPacket(ServerOP_ServerReloadRequest, sizeof(ServerReload::Request));
|
static auto pack = ServerPacket(ServerOP_ServerReloadRequest, sizeof(ServerReload::Request));
|
||||||
@@ -894,6 +947,8 @@ void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
|
|||||||
ServerReload::Type::Commands,
|
ServerReload::Type::Commands,
|
||||||
ServerReload::Type::PerlExportSettings,
|
ServerReload::Type::PerlExportSettings,
|
||||||
ServerReload::Type::DataBucketsCache,
|
ServerReload::Type::DataBucketsCache,
|
||||||
|
ServerReload::Type::Quests,
|
||||||
|
ServerReload::Type::QuestsTimerReset,
|
||||||
ServerReload::Type::WorldRepop,
|
ServerReload::Type::WorldRepop,
|
||||||
ServerReload::Type::WorldWithRespawn
|
ServerReload::Type::WorldWithRespawn
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ public:
|
|||||||
bool SendPacket(ServerPacket *pack);
|
bool SendPacket(ServerPacket *pack);
|
||||||
bool SendPacket(uint32 zoneid, ServerPacket *pack);
|
bool SendPacket(uint32 zoneid, ServerPacket *pack);
|
||||||
bool SendPacket(uint32 zoneid, uint16 instanceid, 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 SendPacketToBootedZones(ServerPacket* pack);
|
||||||
bool SetLockedZone(uint16 iZoneID, bool iLock);
|
bool SetLockedZone(uint16 iZoneID, bool iLock);
|
||||||
|
|
||||||
@@ -70,6 +72,7 @@ public:
|
|||||||
ZoneServer* FindByZoneID(uint32 ZoneID);
|
ZoneServer* FindByZoneID(uint32 ZoneID);
|
||||||
|
|
||||||
const std::list<std::unique_ptr<ZoneServer>> &getZoneServerList() const;
|
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);
|
void SendServerReload(ServerReload::Type type, uchar *packet = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
+15
-7
@@ -571,8 +571,14 @@ 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;
|
break;
|
||||||
}
|
}
|
||||||
@@ -729,13 +735,15 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
|||||||
zs = zoneserver_list.FindByID(s->zone_server_id);
|
zs = zoneserver_list.FindByID(s->zone_server_id);
|
||||||
} else if (s->zone_id) {
|
} else if (s->zone_id) {
|
||||||
zs = zoneserver_list.FindByName(ZoneName(s->zone_id));
|
zs = zoneserver_list.FindByName(ZoneName(s->zone_id));
|
||||||
|
} else if (s->instance_id) {
|
||||||
|
zs = zoneserver_list.FindByInstanceID(s->instance_id);
|
||||||
} else {
|
} else {
|
||||||
zoneserver_list.SendEmoteMessage(
|
zoneserver_list.SendEmoteMessage(
|
||||||
s->admin_name,
|
s->admin_name,
|
||||||
0,
|
0,
|
||||||
AccountStatus::Player,
|
AccountStatus::Player,
|
||||||
Chat::White,
|
Chat::White,
|
||||||
"Error: SOP_ZoneShutdown: neither ID nor name specified"
|
"Error: SOP_ZoneShutdown: Zone ID, Instance ID, nor Zone Short Name specified"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1510,7 +1518,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
|||||||
|
|
||||||
guild->tribute.timer.Disable();
|
guild->tribute.timer.Disable();
|
||||||
|
|
||||||
zoneserver_list.SendPacketToBootedZones(pack);
|
zoneserver_list.SendPacketToZonesWithGuild(data->guild_id, pack);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1553,7 +1561,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
|||||||
guild_mgr.UpdateDbGuildFavor(data->guild_id, data->favor);
|
guild_mgr.UpdateDbGuildFavor(data->guild_id, data->favor);
|
||||||
guild_mgr.UpdateDbTributeTimeRemaining(data->guild_id, data->time_remaining);
|
guild_mgr.UpdateDbTributeTimeRemaining(data->guild_id, data->time_remaining);
|
||||||
|
|
||||||
zoneserver_list.SendPacketToBootedZones(pack);
|
zoneserver_list.SendPacketToZonesWithGuild(data->guild_id, pack);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1587,7 +1595,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
|||||||
data->time_remaining = in->time_remaining;
|
data->time_remaining = in->time_remaining;
|
||||||
strn0cpy(data->player_name, in->player_name, sizeof(data->player_name));
|
strn0cpy(data->player_name, in->player_name, sizeof(data->player_name));
|
||||||
|
|
||||||
zoneserver_list.SendPacketToBootedZones(out);
|
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, out);
|
||||||
safe_delete(out);
|
safe_delete(out);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1610,7 +1618,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
|||||||
out->tribute_id_2_tier = guild->tribute.id_2_tier;
|
out->tribute_id_2_tier = guild->tribute.id_2_tier;
|
||||||
out->time_remaining = guild_mgr.GetGuildTributeTimeRemaining(in->guild_id);
|
out->time_remaining = guild_mgr.GetGuildTributeTimeRemaining(in->guild_id);
|
||||||
|
|
||||||
zoneserver_list.SendPacketToBootedZones(sp);
|
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, sp);
|
||||||
safe_delete(sp);
|
safe_delete(sp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1630,7 +1638,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
|||||||
out->tribute_timer = guild_mgr.GetGuildTributeTimeRemaining(in->guild_id);
|
out->tribute_timer = guild_mgr.GetGuildTributeTimeRemaining(in->guild_id);
|
||||||
out->trophy_timer = 0;
|
out->trophy_timer = 0;
|
||||||
|
|
||||||
zoneserver_list.SendPacketToBootedZones(sp);
|
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, sp);
|
||||||
safe_delete(sp);
|
safe_delete(sp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1654,7 +1662,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
|||||||
out->member_time = in->member_time;
|
out->member_time = in->member_time;
|
||||||
strn0cpy(out->player_name, in->player_name, sizeof(out->player_name));
|
strn0cpy(out->player_name, in->player_name, sizeof(out->player_name));
|
||||||
|
|
||||||
zoneserver_list.SendPacketToBootedZones(sp);
|
zoneserver_list.SendPacketToZonesWithGuild(out->guild_id, sp);
|
||||||
safe_delete(sp)
|
safe_delete(sp)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ public:
|
|||||||
|
|
||||||
inline const char* GetZoneName() const { return zone_name; }
|
inline const char* GetZoneName() const { return zone_name; }
|
||||||
inline const char* GetZoneLongName() const { return long_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; }
|
const char* GetCompileTime() const{ return compiled; }
|
||||||
void SetCompile(char* in_compile){ strcpy(compiled,in_compile); }
|
void SetCompile(char* in_compile){ strcpy(compiled,in_compile); }
|
||||||
inline uint32 GetZoneID() const { return zone_server_zone_id; }
|
inline uint32 GetZoneID() const { return zone_server_zone_id; }
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ SET(zone_sources
|
|||||||
zone_event_scheduler.cpp
|
zone_event_scheduler.cpp
|
||||||
zone_npc_factions.cpp
|
zone_npc_factions.cpp
|
||||||
zone_reload.cpp
|
zone_reload.cpp
|
||||||
|
zone_save_state.cpp
|
||||||
zoning.cpp
|
zoning.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -292,6 +293,7 @@ SET(zone_headers
|
|||||||
zonedump.h
|
zonedump.h
|
||||||
zone_cli.h
|
zone_cli.h
|
||||||
zone_reload.h
|
zone_reload.h
|
||||||
|
zone_save_state.h
|
||||||
zone_cli.cpp)
|
zone_cli.cpp)
|
||||||
|
|
||||||
ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers})
|
ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers})
|
||||||
|
|||||||
@@ -2507,6 +2507,12 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_resumed_from_zone_suspend && !IsQueuedForCorpse()) {
|
||||||
|
LogInfo("NPC [{}] is resumed from zone suspend, cannot kill until zone resume is complete.", GetCleanName());
|
||||||
|
SetHP(0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (IsMultiQuestEnabled()) {
|
if (IsMultiQuestEnabled()) {
|
||||||
for (auto &i: m_hand_in.items) {
|
for (auto &i: m_hand_in.items) {
|
||||||
if (i.is_multiquest_item && i.item->GetItem()->NoDrop != 0) {
|
if (i.is_multiquest_item && i.item->GetItem()->NoDrop != 0) {
|
||||||
@@ -3041,9 +3047,15 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
|||||||
if (!other)
|
if (!other)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (other->IsDestroying())
|
||||||
|
return;
|
||||||
|
|
||||||
if (other == this)
|
if (other == this)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (other->IsClient() && (other->CastToClient()->IsZoning() || other->CastToClient()->Connected() == false))
|
||||||
|
return;
|
||||||
|
|
||||||
if (other->IsTrap())
|
if (other->IsTrap())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
+9
-12
@@ -1165,10 +1165,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case SE_ResistFearChance: {
|
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
|
newbon->ResistFearChance += base_value; // these should stack
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2474,9 +2470,6 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
|
|||||||
|
|
||||||
case SE_ResistFearChance:
|
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
|
new_bonus->ResistFearChance += effect_value; // these should stack
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -4689,11 +4682,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case SE_ResistFearChance:
|
case SE_ResistFearChance:
|
||||||
if (negate_spellbonus) {
|
if (negate_spellbonus) {spellbonuses.ResistFearChance = effect_value; }
|
||||||
spellbonuses.Fearless = false;
|
|
||||||
spellbonuses.ResistFearChance = effect_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (negate_aabonus) { aabonuses.ResistFearChance = effect_value; }
|
if (negate_aabonus) { aabonuses.ResistFearChance = effect_value; }
|
||||||
if (negate_itembonus) { itembonuses.ResistFearChance = effect_value; }
|
if (negate_itembonus) { itembonuses.ResistFearChance = effect_value; }
|
||||||
break;
|
break;
|
||||||
@@ -5331,6 +5320,14 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
|
|||||||
spellbonuses.SEResist[e] = effect_value;
|
spellbonuses.SEResist[e] = effect_value;
|
||||||
spellbonuses.SEResist[e + 1] = 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;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+442
-288
File diff suppressed because it is too large
Load Diff
+5
-33
@@ -39,9 +39,6 @@
|
|||||||
|
|
||||||
constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds
|
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;
|
constexpr uint32 MAG_EPIC_1_0 = 28034;
|
||||||
|
|
||||||
extern WorldServer worldserver;
|
extern WorldServer worldserver;
|
||||||
@@ -232,21 +229,6 @@ static std::map<uint16, std::string> botSubType_names = {
|
|||||||
{ CommandedSubTypes::Selo, "Selo" }
|
{ 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 {
|
class Bot : public NPC {
|
||||||
friend class Mob;
|
friend class Mob;
|
||||||
public:
|
public:
|
||||||
@@ -577,7 +559,7 @@ public:
|
|||||||
uint16 GetPetBotSpellType(uint16 spell_type);
|
uint16 GetPetBotSpellType(uint16 spell_type);
|
||||||
|
|
||||||
// Movement checks
|
// Movement checks
|
||||||
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);
|
bool PlotBotPositionAroundTarget(const FindPositionInput& input);
|
||||||
std::vector<Mob*> GetSpellTargetList(bool entire_raid = false);
|
std::vector<Mob*> GetSpellTargetList(bool entire_raid = false);
|
||||||
void SetSpellTargetList(std::vector<Mob*> spell_target_list) { _spell_target_list = spell_target_list; }
|
void SetSpellTargetList(std::vector<Mob*> spell_target_list) { _spell_target_list = spell_target_list; }
|
||||||
std::vector<Mob*> GetGroupSpellTargetList() { return _group_spell_target_list; }
|
std::vector<Mob*> GetGroupSpellTargetList() { return _group_spell_target_list; }
|
||||||
@@ -683,7 +665,6 @@ public:
|
|||||||
void SetSitManaPct(uint8 value) { _SitManaPct = value; }
|
void SetSitManaPct(uint8 value) { _SitManaPct = value; }
|
||||||
|
|
||||||
// Spell lists
|
// Spell lists
|
||||||
void CheckBotSpells();
|
|
||||||
std::list<BotSpellTypeOrder> GetSpellTypesPrioritized(uint8 priority_type);
|
std::list<BotSpellTypeOrder> GetSpellTypesPrioritized(uint8 priority_type);
|
||||||
static uint16 GetParentSpellType(uint16 spell_type);
|
static uint16 GetParentSpellType(uint16 spell_type);
|
||||||
static bool IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id);
|
static bool IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id);
|
||||||
@@ -1103,15 +1084,8 @@ public:
|
|||||||
bool CheckIfCasting(float fm_distance);
|
bool CheckIfCasting(float fm_distance);
|
||||||
void HealRotationChecks();
|
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();
|
void SetCombatJitter();
|
||||||
void SetCombatOutOfRangeJitter();
|
bool DoCombatPositioning(const CombatPositioningInput& input);
|
||||||
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);
|
void RunToGoalWithJitter(glm::vec3 Goal);
|
||||||
bool RequiresLoSForPositioning();
|
bool RequiresLoSForPositioning();
|
||||||
bool HasRequiredLoSForPositioning(Mob* tar);
|
bool HasRequiredLoSForPositioning(Mob* tar);
|
||||||
@@ -1119,12 +1093,12 @@ public:
|
|||||||
// Try Combat Methods
|
// Try Combat Methods
|
||||||
bool TryEvade(Mob* tar);
|
bool TryEvade(Mob* tar);
|
||||||
bool TryFacingTarget(Mob* tar);
|
bool TryFacingTarget(Mob* tar);
|
||||||
bool TryPursueTarget(float leash_distance, glm::vec3& Goal);
|
bool TryPursueTarget(float leash_distance);
|
||||||
bool TryMeditate();
|
bool TryMeditate();
|
||||||
bool TryAutoDefend(Client* bot_owner, float leash_distance);
|
bool TryAutoDefend(Client* bot_owner, float leash_distance);
|
||||||
bool TryIdleChecks(float fm_distance);
|
bool TryIdleChecks(float fm_distance);
|
||||||
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal);
|
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob);
|
||||||
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance);
|
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance);
|
||||||
bool TryBardMovementCasts();
|
bool TryBardMovementCasts();
|
||||||
bool BotRangedAttack(Mob* other, bool can_double_attack = false);
|
bool BotRangedAttack(Mob* other, bool can_double_attack = false);
|
||||||
bool CheckDoubleRangedAttack();
|
bool CheckDoubleRangedAttack();
|
||||||
@@ -1190,8 +1164,6 @@ private:
|
|||||||
Timer m_auto_save_timer;
|
Timer m_auto_save_timer;
|
||||||
|
|
||||||
Timer m_combat_jitter_timer;
|
Timer m_combat_jitter_timer;
|
||||||
bool m_combat_jitter_flag;
|
|
||||||
bool m_combat_out_of_range_jitter_flag;
|
|
||||||
|
|
||||||
bool m_dirtyautohaters;
|
bool m_dirtyautohaters;
|
||||||
bool m_guard_flag;
|
bool m_guard_flag;
|
||||||
|
|||||||
@@ -220,6 +220,11 @@ int bot_command_init(void)
|
|||||||
std::vector<std::pair<std::string, uint8>> injected_bot_command_settings;
|
std::vector<std::pair<std::string, uint8>> injected_bot_command_settings;
|
||||||
std::vector<std::string> orphaned_bot_command_settings;
|
std::vector<std::string> orphaned_bot_command_settings;
|
||||||
|
|
||||||
|
if (RuleB(Bots, RunSpellTypeChecksOnBoot)) {
|
||||||
|
LogBotSpellTypeChecks("Running SpellType checks. There may be some spells that are mislabeled as incorrect. Use this as a loose guideline.");
|
||||||
|
database.botdb.CheckBotSpells();
|
||||||
|
}
|
||||||
|
|
||||||
database.botdb.MapCommandedSpellTypeMinLevels();
|
database.botdb.MapCommandedSpellTypeMinLevels();
|
||||||
|
|
||||||
for (auto bcs_iter : bot_command_settings) {
|
for (auto bcs_iter : bot_command_settings) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ void bot_command_attack(Client *c, const Seperator *sep)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!c->DoLosChecks(target_mob)) {
|
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) {
|
||||||
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
|
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -525,6 +525,9 @@ void bot_command_cast(Client* c, const Seperator* sep)
|
|||||||
continue;
|
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)) {
|
if (!bot_iter->AttemptAACastSpell(tar, spell_id, rank)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -543,6 +546,9 @@ void bot_command_cast(Client* c, const Seperator* sep)
|
|||||||
tar = bot_iter;
|
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 (bot_iter->AttemptForcedCastSpell(tar, chosen_spell_id)) {
|
||||||
if (!first_found) {
|
if (!first_found) {
|
||||||
first_found = bot_iter;
|
first_found = bot_iter;
|
||||||
@@ -556,6 +562,7 @@ void bot_command_cast(Client* c, const Seperator* sep)
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
bot_iter->SetCommandedSpell(true);
|
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 (bot_iter->AICastSpell(new_tar, 100, spell_type, sub_target_type, sub_type)) {
|
||||||
if (!first_found) {
|
if (!first_found) {
|
||||||
|
|||||||
@@ -48,9 +48,10 @@ void bot_command_click_item(Client* c, const Seperator* sep)
|
|||||||
sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end());
|
sbl.erase(std::remove(sbl.begin(), sbl.end(), nullptr), sbl.end());
|
||||||
|
|
||||||
Mob* tar = c->GetTarget();
|
Mob* tar = c->GetTarget();
|
||||||
|
bool is_success = false;
|
||||||
|
|
||||||
for (auto my_bot : sbl) {
|
for (auto my_bot : sbl) {
|
||||||
if (my_bot->BotPassiveCheck()) {
|
if (!my_bot->ValidStateCheck(c)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +69,11 @@ void bot_command_click_item(Client* c, const Seperator* sep)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_success = true;
|
||||||
my_bot->TryItemClick(slot_id);
|
my_bot->TryItemClick(slot_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!is_success) {
|
||||||
|
c->Message(Chat::Yellow, "None of your bots are capable of doing that currently.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,7 +197,11 @@ void bot_command_depart(Client* c, const Seperator* sep)
|
|||||||
|
|
||||||
bot_iter->SetCommandedSpell(true);
|
bot_iter->SetCommandedSpell(true);
|
||||||
|
|
||||||
if (!IsValidSpellAndLoS(itr->SpellId, bot_iter->HasLoS())) {
|
if (!IsValidSpell(itr->SpellId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BotRequiresLoSToCast(BotSpellTypes::Teleport, itr->SpellId) && !bot_iter->HasLoS()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ void bot_command_precombat(Client* c, const Seperator* sep)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!c->DoLosChecks(c->GetTarget())) {
|
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(c->GetTarget())) {
|
||||||
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
|
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!c->DoLosChecks(target_mob)) {
|
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) {
|
||||||
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
|
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -2523,6 +2523,142 @@ bool BotDatabase::DeleteBotBlockedBuffs(const uint32 bot_id)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BotDatabase::CheckBotSpells() {
|
||||||
|
auto spell_list = BotSpellsEntriesRepository::All(content_db);
|
||||||
|
uint16 spell_id;
|
||||||
|
SPDat_Spell_Struct spell;
|
||||||
|
|
||||||
|
for (const auto& s : spell_list) {
|
||||||
|
if (!IsValidSpell(s.spell_id)) {
|
||||||
|
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
spell = spells[s.spell_id];
|
||||||
|
spell_id = spell.id;
|
||||||
|
|
||||||
|
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) {
|
||||||
|
LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) {
|
||||||
|
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}.",
|
||||||
|
GetSpellName(spell_id),
|
||||||
|
spell_id,
|
||||||
|
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||||
|
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||||
|
s.npc_spells_id,
|
||||||
|
s.minlevel
|
||||||
|
);
|
||||||
|
|
||||||
|
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
|
||||||
|
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||||
|
spell_id,
|
||||||
|
s.npc_spells_id,
|
||||||
|
GetSpellName(spell_id),
|
||||||
|
spell_id,
|
||||||
|
s.minlevel,
|
||||||
|
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||||
|
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||||
|
s.npc_spells_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) {
|
||||||
|
LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}.",
|
||||||
|
GetSpellName(spell_id),
|
||||||
|
spell_id,
|
||||||
|
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||||
|
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||||
|
s.npc_spells_id,
|
||||||
|
s.minlevel
|
||||||
|
);
|
||||||
|
|
||||||
|
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
|
||||||
|
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||||
|
spell_id,
|
||||||
|
s.npc_spells_id,
|
||||||
|
GetSpellName(spell_id),
|
||||||
|
spell_id,
|
||||||
|
s.minlevel,
|
||||||
|
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||||
|
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||||
|
s.npc_spells_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) {
|
||||||
|
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}.",
|
||||||
|
GetSpellName(spell_id),
|
||||||
|
spell_id,
|
||||||
|
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
|
||||||
|
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
|
||||||
|
s.npc_spells_id,
|
||||||
|
s.maxlevel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16 correct_type = GetCorrectBotSpellType(s.type, spell_id);
|
||||||
|
|
||||||
|
if (RuleB(Bots, UseParentSpellTypeForChecks)) {
|
||||||
|
uint16 parent_type = Bot::GetParentSpellType(correct_type);
|
||||||
|
|
||||||
|
if (s.type == parent_type || s.type == correct_type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (correct_type != parent_type) {
|
||||||
|
correct_type = parent_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (IsPetBotSpellType(s.type)) {
|
||||||
|
correct_type = GetPetBotSpellType(correct_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsPetBotSpellType(correct_type) && (spell.target_type != ST_Pet && spell.target_type != ST_SummonedPet)) {
|
||||||
|
correct_type = Bot::GetParentSpellType(correct_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (correct_type == s.type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (correct_type == UINT16_MAX) {
|
||||||
|
LogBotSpellTypeChecks(
|
||||||
|
"{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown.",
|
||||||
|
GetSpellName(spell_id),
|
||||||
|
spell_id,
|
||||||
|
Bot::GetSpellTypeNameByID(s.type),
|
||||||
|
s.type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]",
|
||||||
|
GetSpellName(spell_id),
|
||||||
|
spell_id,
|
||||||
|
Bot::GetSpellTypeNameByID(s.type),
|
||||||
|
s.type,
|
||||||
|
Bot::GetSpellTypeNameByID(correct_type),
|
||||||
|
correct_type
|
||||||
|
);
|
||||||
|
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]",
|
||||||
|
correct_type,
|
||||||
|
spell_id,
|
||||||
|
GetSpellName(spell_id),
|
||||||
|
spell_id,
|
||||||
|
Bot::GetSpellTypeNameByID(s.type),
|
||||||
|
s.type,
|
||||||
|
Bot::GetSpellTypeNameByID(correct_type),
|
||||||
|
correct_type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BotDatabase::MapCommandedSpellTypeMinLevels() {
|
void BotDatabase::MapCommandedSpellTypeMinLevels() {
|
||||||
commanded_spell_type_min_levels.clear();
|
commanded_spell_type_min_levels.clear();
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ public:
|
|||||||
bool SaveBotSettings(Mob* m);
|
bool SaveBotSettings(Mob* m);
|
||||||
bool DeleteBotSettings(const uint32 bot_id);
|
bool DeleteBotSettings(const uint32 bot_id);
|
||||||
|
|
||||||
|
void CheckBotSpells();
|
||||||
void MapCommandedSpellTypeMinLevels();
|
void MapCommandedSpellTypeMinLevels();
|
||||||
std::map<int32_t, std::map<int32_t, BotSpellTypesByClass>> GetCommandedSpellTypesMinLevels() { return commanded_spell_type_min_levels; }
|
std::map<int32_t, std::map<int32_t, BotSpellTypesByClass>> GetCommandedSpellTypesMinLevels() { return commanded_spell_type_min_levels; }
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "../common/types.h"
|
#include "../common/types.h"
|
||||||
#include "../common/timer.h"
|
#include "../common/timer.h"
|
||||||
|
#include "mob.h"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
@@ -151,4 +152,39 @@ struct BotSpellTypesByClass {
|
|||||||
std::string description;
|
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
|
#endif // BOT_STRUCTS
|
||||||
|
|||||||
+87
-198
@@ -41,7 +41,10 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chance < 100 && zone->random.Int(0, 100) > chance) {
|
if (
|
||||||
|
!IsCommandedSpell() &&
|
||||||
|
zone->random.Int(0, 100) > chance
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,10 +64,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
|
|||||||
bot_spell.SpellIndex = 0;
|
bot_spell.SpellIndex = 0;
|
||||||
bot_spell.ManaCost = 0;
|
bot_spell.ManaCost = 0;
|
||||||
|
|
||||||
if (BotSpellTypeRequiresLoS(spell_type) && tar != this) {
|
if (!BotSpellTypeRequiresLoS(spell_type) || tar == this) {
|
||||||
SetHasLoS(DoLosChecks(tar));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SetHasLoS(true);
|
SetHasLoS(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,8 +218,11 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
|
|||||||
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, (IsAEBotSpellType(spell_type) || sub_target_type == CommandedSubTypes::AETarget), sub_target_type, sub_type);
|
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) {
|
for (const auto& s : bot_spell_list) {
|
||||||
|
if (!IsValidSpell(s.SpellId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
|
if (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +276,11 @@ bool Bot::BotCastMez(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
|
|||||||
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type));
|
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type));
|
||||||
|
|
||||||
for (const auto& s : bot_spell_list) {
|
for (const auto& s : bot_spell_list) {
|
||||||
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
|
if (!IsValidSpell(s.SpellId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +288,7 @@ bool Bot::BotCastMez(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
|
|||||||
Mob* add_mob = GetFirstIncomingMobToMez(this, s.SpellId, spell_type, IsAEBotSpellType(spell_type));
|
Mob* add_mob = GetFirstIncomingMobToMez(this, s.SpellId, spell_type, IsAEBotSpellType(spell_type));
|
||||||
|
|
||||||
if (!add_mob) {
|
if (!add_mob) {
|
||||||
return false;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tar = add_mob;
|
tar = add_mob;
|
||||||
@@ -327,7 +334,11 @@ bool Bot::BotCastCure(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spe
|
|||||||
|
|
||||||
bot_spell = GetBestBotSpellForCure(this, tar, spell_type);
|
bot_spell = GetBestBotSpellForCure(this, tar, spell_type);
|
||||||
|
|
||||||
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
|
if (!IsValidSpell(bot_spell.SpellId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,7 +408,11 @@ bool Bot::BotCastPet(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
|
|||||||
bot_spell = GetFirstBotSpellBySpellType(this, spell_type);
|
bot_spell = GetFirstBotSpellBySpellType(this, spell_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
|
if (!IsValidSpell(bot_spell.SpellId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,6 +433,10 @@ bool Bot::BotCastPet(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Bot::BotCastNuke(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type) {
|
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) {
|
if (spell_type == BotSpellTypes::Stun || spell_type == BotSpellTypes::AEStun) {
|
||||||
uint8 stun_chance = (tar->IsCasting() ? RuleI(Bots, StunCastChanceIfCasting) : RuleI(Bots, StunCastChanceNormal));
|
uint8 stun_chance = (tar->IsCasting() ? RuleI(Bots, StunCastChanceIfCasting) : RuleI(Bots, StunCastChanceNormal));
|
||||||
|
|
||||||
@@ -435,24 +454,24 @@ 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);
|
bot_spell = GetBestBotSpellForStunByTargetType(this, ST_TargetOptional, spell_type, IsAEBotSpellType(spell_type), tar);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
|
if (!IsValidSpell(bot_spell.SpellId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
|
if (!IsValidSpell(bot_spell.SpellId)) {
|
||||||
bot_spell = GetBestBotSpellForNukeByBodyType(this, tar->GetBodyType(), spell_type, IsAEBotSpellType(spell_type), tar);
|
bot_spell = GetBestBotSpellForNukeByBodyType(this, tar->GetBodyType(), spell_type, IsAEBotSpellType(spell_type), tar);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS()) && spell_type == BotSpellTypes::Nuke && bot_class == Class::Wizard) {
|
if (spell_type == BotSpellTypes::Nuke && bot_class == Class::Wizard && !IsValidSpell(bot_spell.SpellId)) {
|
||||||
bot_spell = GetBestBotWizardNukeSpellByTargetResists(this, tar, spell_type);
|
bot_spell = GetBestBotWizardNukeSpellByTargetResists(this, tar, spell_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
|
if (!IsValidSpell(bot_spell.SpellId)) {
|
||||||
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type));
|
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type));
|
||||||
|
|
||||||
for (const auto& s : bot_spell_list) {
|
for (const auto& s : bot_spell_list) {
|
||||||
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
|
if (!IsValidSpell(s.SpellId)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,7 +581,9 @@ bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
|
|||||||
// Allow bots to cast buff spells even if they are out of mana
|
// Allow bots to cast buff spells even if they are out of mana
|
||||||
if (
|
if (
|
||||||
RuleB(Bots, FinishBuffing) &&
|
RuleB(Bots, FinishBuffing) &&
|
||||||
manaCost > hasMana && AIBot_spells[i].type == BotSpellTypes::Buff
|
manaCost > hasMana &&
|
||||||
|
!IsEngaged() &&
|
||||||
|
IsBotBuffSpellType(AIBot_spells[i].type)
|
||||||
) {
|
) {
|
||||||
SetMana(manaCost);
|
SetMana(manaCost);
|
||||||
}
|
}
|
||||||
@@ -908,7 +929,11 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffect(Bot* caster, uint16 spell_ty
|
|||||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||||
|
|
||||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -946,7 +971,11 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* caster, ui
|
|||||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||||
|
|
||||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -987,7 +1016,11 @@ std::list<BotSpell> Bot::GetBotSpellsBySpellType(Bot* caster, uint16 spell_type)
|
|||||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||||
|
|
||||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1016,7 +1049,11 @@ std::vector<BotSpell_wPriority> Bot::GetPrioritizedBotSpellsBySpellType(Bot* cas
|
|||||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||||
|
|
||||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1103,7 +1140,11 @@ BotSpell Bot::GetFirstBotSpellBySpellType(Bot* caster, uint16 spell_type) {
|
|||||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||||
|
|
||||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1135,7 +1176,6 @@ BotSpell Bot::GetBestBotSpellForVeryFastHeal(Bot* caster, Mob* tar, uint16 spell
|
|||||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
|
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
|
||||||
|
|
||||||
for (auto bot_spell_list_itr : bot_spell_list) {
|
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 (
|
if (
|
||||||
IsVeryFastHealSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) {
|
IsVeryFastHealSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) {
|
||||||
result.SpellId = bot_spell_list_itr.SpellId;
|
result.SpellId = bot_spell_list_itr.SpellId;
|
||||||
@@ -1161,7 +1201,6 @@ BotSpell Bot::GetBestBotSpellForFastHeal(Bot* caster, Mob* tar, uint16 spell_typ
|
|||||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
|
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
|
||||||
|
|
||||||
for (auto bot_spell_list_itr : bot_spell_list) {
|
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)) {
|
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.SpellId = bot_spell_list_itr.SpellId;
|
||||||
result.SpellIndex = bot_spell_list_itr.SpellIndex;
|
result.SpellIndex = bot_spell_list_itr.SpellIndex;
|
||||||
@@ -1186,7 +1225,6 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* caster, Mob* tar, uint16 spell
|
|||||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_HealOverTime);
|
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_HealOverTime);
|
||||||
|
|
||||||
for (auto bot_spell_list_itr : bot_spell_list) {
|
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)) {
|
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.SpellId = bot_spell_list_itr.SpellId;
|
||||||
result.SpellIndex = bot_spell_list_itr.SpellIndex;
|
result.SpellIndex = bot_spell_list_itr.SpellIndex;
|
||||||
@@ -1243,7 +1281,6 @@ BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* caster, Mob* tar, u
|
|||||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
|
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) {
|
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)) {
|
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.SpellId = bot_spell_list_itr->SpellId;
|
||||||
result.SpellIndex = bot_spell_list_itr->SpellIndex;
|
result.SpellIndex = bot_spell_list_itr->SpellIndex;
|
||||||
@@ -1268,7 +1305,6 @@ BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* caster, Mob* tar, uint16
|
|||||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
|
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) {
|
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)) {
|
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.SpellId = bot_spell_list_itr->SpellId;
|
||||||
result.SpellIndex = bot_spell_list_itr->SpellIndex;
|
result.SpellIndex = bot_spell_list_itr->SpellIndex;
|
||||||
@@ -1298,7 +1334,6 @@ BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* caster, Mob* tar, uint16 spell_ty
|
|||||||
int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type);
|
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) {
|
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)) {
|
if (IsRegularGroupHealSpell(bot_spell_list_itr->SpellId)) {
|
||||||
uint16 spell_id = bot_spell_list_itr->SpellId;
|
uint16 spell_id = bot_spell_list_itr->SpellId;
|
||||||
|
|
||||||
@@ -1337,7 +1372,6 @@ BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* caster, Mob* tar, uint16
|
|||||||
int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type);
|
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) {
|
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)) {
|
if (IsGroupHealOverTimeSpell(bot_spell_list_itr->SpellId)) {
|
||||||
uint16 spell_id = bot_spell_list_itr->SpellId;
|
uint16 spell_id = bot_spell_list_itr->SpellId;
|
||||||
|
|
||||||
@@ -1376,7 +1410,6 @@ BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* caster, Mob* tar, uint16
|
|||||||
int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type);
|
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) {
|
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)) {
|
if (IsGroupCompleteHealSpell(bot_spell_list_itr->SpellId)) {
|
||||||
uint16 spell_id = bot_spell_list_itr->SpellId;
|
uint16 spell_id = bot_spell_list_itr->SpellId;
|
||||||
|
|
||||||
@@ -1410,7 +1443,6 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* caster, uint16 spell_type) {
|
|||||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Mez);
|
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) {
|
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 (
|
if (
|
||||||
IsMesmerizeSpell(bot_spell_list_itr->SpellId) &&
|
IsMesmerizeSpell(bot_spell_list_itr->SpellId) &&
|
||||||
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)
|
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)
|
||||||
@@ -1433,11 +1465,10 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
|
|||||||
if (caster && caster->GetOwner()) {
|
if (caster && caster->GetOwner()) {
|
||||||
int spell_range = caster->GetActSpellRange(spell_id, spells[spell_id].range);
|
int spell_range = caster->GetActSpellRange(spell_id, spells[spell_id].range);
|
||||||
int spell_ae_range = caster->GetAOERange(spell_id);
|
int spell_ae_range = caster->GetAOERange(spell_id);
|
||||||
int buff_count = 0;
|
bool is_pbae_spell = IsPBAESpell(spell_id);
|
||||||
NPC* npc = nullptr;
|
NPC* npc = nullptr;
|
||||||
|
|
||||||
for (auto& close_mob : caster->m_close_mobs) {
|
for (auto& close_mob : caster->m_close_mobs) {
|
||||||
buff_count = 0;
|
|
||||||
npc = close_mob.second->CastToNPC();
|
npc = close_mob.second->CastToNPC();
|
||||||
|
|
||||||
if (!npc) {
|
if (!npc) {
|
||||||
@@ -1448,30 +1479,30 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
|
|||||||
continue;
|
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) {
|
if (AE) {
|
||||||
int target_count = 0;
|
int target_count = 0;
|
||||||
|
|
||||||
for (auto& close_mob : caster->m_close_mobs) {
|
for (auto& close_mob : caster->m_close_mobs) {
|
||||||
Mob* m = close_mob.second;
|
Mob* m = close_mob.second;
|
||||||
|
|
||||||
if (npc == m) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!caster->IsValidMezTarget(caster->GetOwner(), m, spell_id)) {
|
if (!caster->IsValidMezTarget(caster->GetOwner(), m, spell_id)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsPBAESpell(spell_id)) {
|
if (spell_ae_range < Distance(npc->GetPosition(), m->GetPosition())) {
|
||||||
if (spell_ae_range < Distance(caster->GetPosition(), m->GetPosition())) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (spell_range < Distance(m->GetPosition(), npc->GetPosition())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (caster->CastChecks(spell_id, m, spell_type, true, true)) {
|
if (caster->CastChecks(spell_id, m, spell_type, true, true)) {
|
||||||
++target_count;
|
++target_count;
|
||||||
@@ -1486,11 +1517,6 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zone->random.Int(1, 100) < RuleI(Bots, AEMezChance)) {
|
|
||||||
caster->SetSpellTypeRecastTimer(spell_type, RuleI(Bots, MezFailDelay));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = npc;
|
result = npc;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1502,18 +1528,10 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zone->random.Int(1, 100) < RuleI(Bots, MezChance)) {
|
|
||||||
caster->SetSpellTypeRecastTimer(spell_type, RuleI(Bots, MezAEFailDelay));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = npc;
|
result = npc;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
caster->SetHasLoS(true);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1534,7 +1552,6 @@ BotSpell Bot::GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type) {
|
|||||||
std::string pet_type = GetBotMagicianPetType(caster);
|
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) {
|
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 (
|
if (
|
||||||
IsSummonPetSpell(bot_spell_list_itr->SpellId) &&
|
IsSummonPetSpell(bot_spell_list_itr->SpellId) &&
|
||||||
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId) &&
|
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId) &&
|
||||||
@@ -1716,7 +1733,6 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType ta
|
|||||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffectAndTargetType(caster, spell_type, SE_CurrentHP, target_type);
|
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) {
|
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 (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)) {
|
if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1729,7 +1745,6 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType ta
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
caster->IsCommandedSpell() ||
|
caster->IsCommandedSpell() ||
|
||||||
!AE ||
|
!AE ||
|
||||||
@@ -1766,7 +1781,6 @@ BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* caster, SpellTargetType ta
|
|||||||
|
|
||||||
for(std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr)
|
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 (IsStunSpell(bot_spell_list_itr->SpellId)) {
|
||||||
if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) {
|
if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1830,7 +1844,6 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target,
|
|||||||
bool spell_selected = false;
|
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) {
|
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)) {
|
if (!caster->IsValidSpellRange(bot_spell_list_itr->SpellId, target)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1887,8 +1900,6 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target,
|
|||||||
|
|
||||||
if (!spell_selected) {
|
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) {
|
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->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)) {
|
||||||
if (caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)) {
|
if (caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)) {
|
||||||
spell_selected = true;
|
spell_selected = true;
|
||||||
@@ -1926,7 +1937,11 @@ BotSpell Bot::GetDebuffBotSpell(Bot* caster, Mob *tar, uint16 spell_type) {
|
|||||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||||
|
|
||||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1972,7 +1987,11 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* caster, Mob *tar, uint16 spell
|
|||||||
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
|
||||||
|
|
||||||
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
|
||||||
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
|
if (!IsValidSpell(bot_spell_list[i].spellid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2091,7 +2110,6 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spell_type)
|
|||||||
case BotSpellTypes::AERains:
|
case BotSpellTypes::AERains:
|
||||||
case BotSpellTypes::AEStun:
|
case BotSpellTypes::AEStun:
|
||||||
case BotSpellTypes::AESnare:
|
case BotSpellTypes::AESnare:
|
||||||
case BotSpellTypes::AEMez:
|
|
||||||
case BotSpellTypes::AESlow:
|
case BotSpellTypes::AESlow:
|
||||||
case BotSpellTypes::AEDebuff:
|
case BotSpellTypes::AEDebuff:
|
||||||
case BotSpellTypes::AEFear:
|
case BotSpellTypes::AEFear:
|
||||||
@@ -2157,6 +2175,8 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spell_type)
|
|||||||
case BotSpellTypes::PetVeryFastHeals:
|
case BotSpellTypes::PetVeryFastHeals:
|
||||||
case BotSpellTypes::PetHoTHeals:
|
case BotSpellTypes::PetHoTHeals:
|
||||||
return RuleI(Bots, PercentChanceToCastHeal);
|
return RuleI(Bots, PercentChanceToCastHeal);
|
||||||
|
case BotSpellTypes::AEMez:
|
||||||
|
return RuleI(Bots, PercentChanceToCastAEMez);
|
||||||
default:
|
default:
|
||||||
return RuleI(Bots, PercentChanceToCastOtherType);
|
return RuleI(Bots, PercentChanceToCastOtherType);
|
||||||
}
|
}
|
||||||
@@ -2821,7 +2841,6 @@ BotSpell Bot::GetBestBotSpellForRez(Bot* caster, Mob* target, uint16 spell_type)
|
|||||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Revive);
|
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) {
|
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 (
|
if (
|
||||||
IsResurrectSpell(bot_spell_list_itr->SpellId) &&
|
IsResurrectSpell(bot_spell_list_itr->SpellId) &&
|
||||||
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)
|
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)
|
||||||
@@ -2849,7 +2868,6 @@ BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_typ
|
|||||||
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Charm);
|
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) {
|
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 (
|
if (
|
||||||
IsCharmSpell(bot_spell_list_itr->SpellId) &&
|
IsCharmSpell(bot_spell_list_itr->SpellId) &&
|
||||||
caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)
|
caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)
|
||||||
@@ -2865,132 +2883,3 @@ BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_typ
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Bot::CheckBotSpells() {
|
|
||||||
auto spell_list = BotSpellsEntriesRepository::All(content_db);
|
|
||||||
uint16 spell_id;
|
|
||||||
SPDat_Spell_Struct spell;
|
|
||||||
uint16 correct_type;
|
|
||||||
uint16 parent_type;
|
|
||||||
|
|
||||||
for (const auto& s : spell_list) {
|
|
||||||
if (!IsValidSpell(s.spell_id)) {
|
|
||||||
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
spell = spells[s.spell_id];
|
|
||||||
spell_id = spell.id;
|
|
||||||
|
|
||||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) {
|
|
||||||
LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) {
|
|
||||||
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}."
|
|
||||||
, GetSpellName(spell_id)
|
|
||||||
, spell_id
|
|
||||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
|
||||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
|
||||||
, s.npc_spells_id
|
|
||||||
, s.minlevel
|
|
||||||
);
|
|
||||||
|
|
||||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]"
|
|
||||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
|
||||||
, spell_id
|
|
||||||
, s.npc_spells_id
|
|
||||||
, GetSpellName(spell_id)
|
|
||||||
, spell_id
|
|
||||||
, s.minlevel
|
|
||||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
|
||||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
|
||||||
, s.npc_spells_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) {
|
|
||||||
LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}."
|
|
||||||
, GetSpellName(spell_id)
|
|
||||||
, spell_id
|
|
||||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
|
||||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
|
||||||
, s.npc_spells_id
|
|
||||||
, s.minlevel
|
|
||||||
);
|
|
||||||
|
|
||||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]"
|
|
||||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
|
||||||
, spell_id
|
|
||||||
, s.npc_spells_id
|
|
||||||
, GetSpellName(spell_id)
|
|
||||||
, spell_id
|
|
||||||
, s.minlevel
|
|
||||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
|
||||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
|
||||||
, s.npc_spells_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) {
|
|
||||||
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}."
|
|
||||||
, GetSpellName(spell_id)
|
|
||||||
, spell_id
|
|
||||||
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
|
|
||||||
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
|
|
||||||
, s.npc_spells_id
|
|
||||||
, s.maxlevel
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
correct_type = GetCorrectBotSpellType(s.type, spell_id);
|
|
||||||
parent_type = GetParentSpellType(correct_type);
|
|
||||||
|
|
||||||
if (RuleB(Bots, UseParentSpellTypeForChecks)) {
|
|
||||||
if (s.type == parent_type || s.type == correct_type) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (IsPetBotSpellType(s.type)) {
|
|
||||||
correct_type = GetPetBotSpellType(correct_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (correct_type == s.type) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (correct_type == UINT16_MAX) {
|
|
||||||
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown."
|
|
||||||
, GetSpellName(spell_id)
|
|
||||||
, spell_id
|
|
||||||
, GetSpellTypeNameByID(s.type)
|
|
||||||
, s.type
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]"
|
|
||||||
, GetSpellName(spell_id)
|
|
||||||
, spell_id
|
|
||||||
, GetSpellTypeNameByID(s.type)
|
|
||||||
, s.type
|
|
||||||
, GetSpellTypeNameByID(correct_type)
|
|
||||||
, correct_type
|
|
||||||
);
|
|
||||||
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]"
|
|
||||||
, correct_type
|
|
||||||
, spell_id
|
|
||||||
, GetSpellName(spell_id)
|
|
||||||
, spell_id
|
|
||||||
, GetSpellTypeNameByID(s.type)
|
|
||||||
, s.type
|
|
||||||
, GetSpellTypeNameByID(correct_type)
|
|
||||||
, correct_type
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#include "../../zone.h"
|
||||||
|
|
||||||
|
inline void RunTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
||||||
|
{
|
||||||
|
if (expected == actual) {
|
||||||
|
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||||
|
} else {
|
||||||
|
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||||
|
std::cerr << " 📌 Expected: " << expected << "\n";
|
||||||
|
std::cerr << " ❌ Got: " << actual << "\n";
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void RunTest(const std::string &test_name, bool expected, bool actual)
|
||||||
|
{
|
||||||
|
if (expected == actual) {
|
||||||
|
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||||
|
std::cerr << " 📌 Expected: " << (expected ? "true" : "false") << "\n";
|
||||||
|
std::cerr << " ❌ Got: " << (actual ? "true" : "false") << "\n";
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void RunTest(const std::string &test_name, int expected, int actual)
|
||||||
|
{
|
||||||
|
if (expected == actual) {
|
||||||
|
std::cout << "[✅] " << test_name << " PASSED\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cerr << "[❌] " << test_name << " FAILED\n";
|
||||||
|
std::cerr << " 📌 Expected: " << expected << "\n";
|
||||||
|
std::cerr << " ❌ Got: " << actual << "\n";
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern Zone *zone;
|
||||||
|
|
||||||
|
inline void SetupZone(std::string zone_short_name, uint32 instance_id = 0) {
|
||||||
|
LogSys.SilenceConsoleLogging();
|
||||||
|
|
||||||
|
LogSys.log_settings[Logs::ZoneState].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||||
|
LogSys.log_settings[Logs::Info].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||||
|
LogSys.log_settings[Logs::Spawns].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||||
|
|
||||||
|
// boot shell zone for testing
|
||||||
|
Zone::Bootup(ZoneID(zone_short_name), 0, false);
|
||||||
|
zone->StopShutdownTimer();
|
||||||
|
entity_list.Process();
|
||||||
|
entity_list.MobProcess();
|
||||||
|
|
||||||
|
LogSys.EnableConsoleLogging();
|
||||||
|
}
|
||||||
@@ -1,25 +1,13 @@
|
|||||||
#include "../../common/http/httplib.h"
|
#include "../../common/http/httplib.h"
|
||||||
#include "../../common/eqemu_logsys.h"
|
#include "../../common/eqemu_logsys.h"
|
||||||
#include "../../common/platform.h"
|
#include "../../common/platform.h"
|
||||||
#include "../zone.h"
|
#include "../../zone.h"
|
||||||
#include "../client.h"
|
#include "../../client.h"
|
||||||
#include "../../common/net/eqstream.h"
|
#include "../../common/net/eqstream.h"
|
||||||
|
|
||||||
extern Zone *zone;
|
extern Zone *zone;
|
||||||
|
|
||||||
void RunTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
void ZoneCLI::TestDataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||||
{
|
|
||||||
if (expected == actual) {
|
|
||||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
|
||||||
} else {
|
|
||||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
|
||||||
std::cerr << " 📌 Expected: " << expected << "\n";
|
|
||||||
std::cerr << " ❌ Got: " << actual << "\n";
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZoneCLI::DataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
|
|
||||||
{
|
{
|
||||||
if (cmd[{"-h", "--help"}]) {
|
if (cmd[{"-h", "--help"}]) {
|
||||||
return;
|
return;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#include "../../common/http/httplib.h"
|
#include "../../common/http/httplib.h"
|
||||||
#include "../../common/eqemu_logsys.h"
|
#include "../../common/eqemu_logsys.h"
|
||||||
#include "../../common/platform.h"
|
#include "../../common/platform.h"
|
||||||
#include "../zone.h"
|
#include "../../zone.h"
|
||||||
#include "../client.h"
|
#include "../../client.h"
|
||||||
#include "../../common/net/eqstream.h"
|
#include "../../common/net/eqstream.h"
|
||||||
#include "../../common/json/json.hpp"
|
#include "../../common/json/json.hpp"
|
||||||
|
|
||||||
@@ -36,19 +36,6 @@ struct TestCase {
|
|||||||
bool handin_check_result;
|
bool handin_check_result;
|
||||||
};
|
};
|
||||||
|
|
||||||
void RunTest(const std::string &test_name, bool expected, bool actual)
|
|
||||||
{
|
|
||||||
if (expected == actual) {
|
|
||||||
std::cout << "[✅] " << test_name << " PASSED\n";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
std::cerr << "[❌] " << test_name << " FAILED\n";
|
|
||||||
std::cerr << " 📌 Expected: " << (expected ? "true" : "false") << "\n";
|
|
||||||
std::cerr << " ❌ Got: " << (actual ? "true" : "false") << "\n";
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RunSerializedTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
void RunSerializedTest(const std::string &test_name, const std::string &expected, const std::string &actual)
|
||||||
{
|
{
|
||||||
if (expected == actual) {
|
if (expected == actual) {
|
||||||
@@ -75,7 +62,7 @@ std::string SerializeHandin(const std::map<std::string, uint32> &items, const Ha
|
|||||||
return j.dump();
|
return j.dump();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
|
void ZoneCLI::TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||||
{
|
{
|
||||||
if (cmd[{"-h", "--help"}]) {
|
if (cmd[{"-h", "--help"}]) {
|
||||||
return;
|
return;
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
#include "../../common/http/httplib.h"
|
#include "../../common/http/httplib.h"
|
||||||
#include "../../common/eqemu_logsys.h"
|
#include "../../common/eqemu_logsys.h"
|
||||||
#include "../../common/platform.h"
|
#include "../../common/platform.h"
|
||||||
#include "../zone.h"
|
#include "../../zone.h"
|
||||||
#include "../client.h"
|
#include "../../client.h"
|
||||||
#include "../../common/net/eqstream.h"
|
#include "../../common/net/eqstream.h"
|
||||||
|
|
||||||
extern Zone *zone;
|
extern Zone *zone;
|
||||||
|
|
||||||
void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description)
|
void ZoneCLI::TestNpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||||
{
|
{
|
||||||
if (cmd[{"-h", "--help"}]) {
|
if (cmd[{"-h", "--help"}]) {
|
||||||
return;
|
return;
|
||||||
File diff suppressed because it is too large
Load Diff
+150
-16
@@ -882,9 +882,13 @@ void Client::SendZoneInPackets()
|
|||||||
//SendGuildMembers();
|
//SendGuildMembers();
|
||||||
SendGuildURL();
|
SendGuildURL();
|
||||||
SendGuildChannel();
|
SendGuildChannel();
|
||||||
|
if (RuleB(Guild, EnableLFGuild)) {
|
||||||
SendGuildLFGuildStatus();
|
SendGuildLFGuildStatus();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (RuleB(Guild, EnableLFGuild)) {
|
||||||
SendLFGuildStatus();
|
SendLFGuildStatus();
|
||||||
|
}
|
||||||
|
|
||||||
//No idea why live sends this if even were not in a guild
|
//No idea why live sends this if even were not in a guild
|
||||||
SendGuildMOTD();
|
SendGuildMOTD();
|
||||||
@@ -991,6 +995,8 @@ bool Client::Save(uint8 iCommitNow) {
|
|||||||
if(!ClientDataLoaded())
|
if(!ClientDataLoaded())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
BenchTimer timer;
|
||||||
|
|
||||||
/* Wrote current basics to PP for saves */
|
/* Wrote current basics to PP for saves */
|
||||||
if (!m_lock_save_position) {
|
if (!m_lock_save_position) {
|
||||||
m_pp.x = m_Position.x;
|
m_pp.x = m_Position.x;
|
||||||
@@ -1018,6 +1024,8 @@ bool Client::Save(uint8 iCommitNow) {
|
|||||||
m_pp.endurance = current_endurance;
|
m_pp.endurance = current_endurance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
database.TransactionBegin();
|
||||||
|
|
||||||
/* Save Character Currency */
|
/* Save Character Currency */
|
||||||
database.SaveCharacterCurrency(CharacterID(), &m_pp);
|
database.SaveCharacterCurrency(CharacterID(), &m_pp);
|
||||||
|
|
||||||
@@ -1101,6 +1109,10 @@ bool Client::Save(uint8 iCommitNow) {
|
|||||||
database.botdb.SaveBotSettings(this);
|
database.botdb.SaveBotSettings(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
database.TransactionCommit();
|
||||||
|
|
||||||
|
LogInfo("Save for [{}] took [{}]", GetCleanName(), timer.elapsed());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1205,6 +1217,58 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s
|
|||||||
|
|
||||||
LogDebug("Client::ChannelMessageReceived() Channel:[{}] message:[{}]", chan_num, message);
|
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) {
|
if (targetname == nullptr) {
|
||||||
targetname = (!GetTarget()) ? "" : GetTarget()->GetName();
|
targetname = (!GetTarget()) ? "" : GetTarget()->GetName();
|
||||||
}
|
}
|
||||||
@@ -2549,40 +2613,59 @@ void Client::ChangeLastName(std::string last_name) {
|
|||||||
safe_delete(outapp);
|
safe_delete(outapp);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Client::ChangeFirstName(const char* in_firstname, const char* gmname)
|
// Deprecated, this packet does not actually work in ROF2
|
||||||
|
bool Client::ChangeFirstName(const std::string in_firstname, const std::string gmname)
|
||||||
{
|
{
|
||||||
// check duplicate name
|
if (!ChangeFirstName(in_firstname)) {
|
||||||
bool used_name = database.IsNameUsed((const char*) in_firstname);
|
|
||||||
if (used_name) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update character_
|
|
||||||
if(!database.UpdateName(GetName(), in_firstname))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// update pp
|
|
||||||
memset(m_pp.name, 0, sizeof(m_pp.name));
|
|
||||||
snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname);
|
|
||||||
strcpy(name, m_pp.name);
|
|
||||||
Save();
|
|
||||||
|
|
||||||
// send name update packet
|
// send name update packet
|
||||||
auto outapp = new EQApplicationPacket(OP_GMNameChange, sizeof(GMName_Struct));
|
auto outapp = new EQApplicationPacket(OP_GMNameChange, sizeof(GMName_Struct));
|
||||||
GMName_Struct* gmn=(GMName_Struct*)outapp->pBuffer;
|
GMName_Struct* gmn=(GMName_Struct*)outapp->pBuffer;
|
||||||
strn0cpy(gmn->gmname,gmname,64);
|
strn0cpy(gmn->gmname,gmname.c_str(),64);
|
||||||
strn0cpy(gmn->oldname,GetName(),64);
|
strn0cpy(gmn->oldname,GetName(),64);
|
||||||
strn0cpy(gmn->newname,in_firstname,64);
|
strn0cpy(gmn->newname,in_firstname.c_str(),64);
|
||||||
gmn->unknown[0] = 1;
|
gmn->unknown[0] = 1;
|
||||||
gmn->unknown[1] = 1;
|
gmn->unknown[1] = 1;
|
||||||
gmn->unknown[2] = 1;
|
gmn->unknown[2] = 1;
|
||||||
entity_list.QueueClients(this, outapp, false);
|
entity_list.QueueClients(this, outapp, false);
|
||||||
safe_delete(outapp);
|
safe_delete(outapp);
|
||||||
|
|
||||||
|
// success
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::ChangeFirstName(const std::string in_firstname)
|
||||||
|
{
|
||||||
|
// check duplicate name
|
||||||
|
bool used_name = database.IsNameUsed(in_firstname) || database.IsPetNameUsed(in_firstname);
|
||||||
|
if (used_name || !database.CheckNameFilter(in_firstname, false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update character_
|
||||||
|
if(!database.UpdateNameByID(CharacterID(), in_firstname))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Send Name Update to Clients
|
||||||
|
SendRename(this, GetName(), in_firstname.c_str());
|
||||||
|
SetName(in_firstname.c_str());
|
||||||
|
|
||||||
|
// update pp
|
||||||
|
memset(m_pp.name, 0, sizeof(m_pp.name));
|
||||||
|
snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname.c_str());
|
||||||
|
strcpy(name, m_pp.name);
|
||||||
|
Save();
|
||||||
|
|
||||||
|
// Update the active char in account table
|
||||||
|
database.UpdateLiveChar(in_firstname, AccountID());
|
||||||
|
|
||||||
// finally, update the /who list
|
// finally, update the /who list
|
||||||
UpdateWho();
|
UpdateWho();
|
||||||
|
|
||||||
// success
|
// success
|
||||||
|
ClearNameChange();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4737,6 +4820,57 @@ bool Client::KeyRingRemove(uint32 item_id)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Client::IsNameChangeAllowed() {
|
||||||
|
if (RuleB(Character, AlwaysAllowNameChange)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto k = GetScopedBucketKeys();
|
||||||
|
k.key = "name_change_allowed";
|
||||||
|
|
||||||
|
auto b = DataBucket::GetData(k);
|
||||||
|
if (!b.value.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::ClearNameChange() {
|
||||||
|
if (!IsNameChangeAllowed()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto k = GetScopedBucketKeys();
|
||||||
|
k.key = "name_change_allowed";
|
||||||
|
|
||||||
|
DataBucket::DeleteData(k);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::InvokeChangeNameWindow(bool immediate) {
|
||||||
|
if (!IsNameChangeAllowed()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto packet_op = immediate ? OP_InvokeNameChangeImmediate : OP_InvokeNameChangeLazy;
|
||||||
|
|
||||||
|
auto outapp = new EQApplicationPacket(packet_op, 0);
|
||||||
|
QueuePacket(outapp);
|
||||||
|
safe_delete(outapp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::GrantNameChange() {
|
||||||
|
|
||||||
|
auto k = GetScopedBucketKeys();
|
||||||
|
k.key = "name_change_allowed";
|
||||||
|
k.value = "allowed"; // potentially put a timestamp here
|
||||||
|
DataBucket::SetData(k);
|
||||||
|
|
||||||
|
InvokeChangeNameWindow(true);
|
||||||
|
}
|
||||||
|
|
||||||
bool Client::IsPetNameChangeAllowed() {
|
bool Client::IsPetNameChangeAllowed() {
|
||||||
if (RuleB(Pets, AlwaysAllowPetRename)) {
|
if (RuleB(Pets, AlwaysAllowPetRename)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+11
-1
@@ -332,6 +332,10 @@ public:
|
|||||||
bool KeyRingClear();
|
bool KeyRingClear();
|
||||||
bool KeyRingRemove(uint32 item_id);
|
bool KeyRingRemove(uint32 item_id);
|
||||||
void KeyRingList();
|
void KeyRingList();
|
||||||
|
bool IsNameChangeAllowed();
|
||||||
|
void InvokeChangeNameWindow(bool immediate = true);
|
||||||
|
bool ClearNameChange();
|
||||||
|
void GrantNameChange();
|
||||||
bool IsPetNameChangeAllowed();
|
bool IsPetNameChangeAllowed();
|
||||||
void GrantPetNameChange();
|
void GrantPetNameChange();
|
||||||
void ClearPetNameChange();
|
void ClearPetNameChange();
|
||||||
@@ -443,6 +447,8 @@ public:
|
|||||||
int64 ValidateBuyLineCost(std::map<uint32, BuylineItemDetails_Struct>& item_map);
|
int64 ValidateBuyLineCost(std::map<uint32, BuylineItemDetails_Struct>& item_map);
|
||||||
bool DoBarterBuyerChecks(BuyerLineSellItem_Struct& sell_line);
|
bool DoBarterBuyerChecks(BuyerLineSellItem_Struct& sell_line);
|
||||||
bool DoBarterSellerChecks(BuyerLineSellItem_Struct& sell_line);
|
bool DoBarterSellerChecks(BuyerLineSellItem_Struct& sell_line);
|
||||||
|
void CancelBuyerTradeWindow();
|
||||||
|
void CancelTraderTradeWindow();
|
||||||
|
|
||||||
void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho);
|
void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho);
|
||||||
bool ShouldISpawnFor(Client *c) { return !GMHideMe(c) && !IsHoveringForRespawn(); }
|
bool ShouldISpawnFor(Client *c) { return !GMHideMe(c) && !IsHoveringForRespawn(); }
|
||||||
@@ -477,6 +483,9 @@ public:
|
|||||||
|
|
||||||
virtual bool Save() { return Save(0); }
|
virtual bool Save() { return Save(0); }
|
||||||
bool Save(uint8 iCommitNow); // 0 = delayed, 1=async now, 2=sync now
|
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 */
|
/* New PP Save Functions */
|
||||||
bool SaveCurrency(){ return database.SaveCharacterCurrency(this->CharacterID(), &m_pp); }
|
bool SaveCurrency(){ return database.SaveCharacterCurrency(this->CharacterID(), &m_pp); }
|
||||||
@@ -509,7 +518,8 @@ public:
|
|||||||
bool AutoAttackEnabled() const { return auto_attack; }
|
bool AutoAttackEnabled() const { return auto_attack; }
|
||||||
bool AutoFireEnabled() const { return auto_fire; }
|
bool AutoFireEnabled() const { return auto_fire; }
|
||||||
|
|
||||||
bool ChangeFirstName(const char* in_firstname,const char* gmname);
|
bool ChangeFirstName(const std::string in_firstname,const std::string gmname);
|
||||||
|
bool ChangeFirstName(const std::string in_firstname);
|
||||||
|
|
||||||
void Duck();
|
void Duck();
|
||||||
void Stand();
|
void Stand();
|
||||||
|
|||||||
+28
-26
@@ -120,48 +120,46 @@ int Client::GetBotSpawnLimit(uint8 class_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto& zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ",");
|
const auto& zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ",");
|
||||||
|
|
||||||
|
if (!zones_list.empty()) {
|
||||||
|
auto it = std::find(zones_list.begin(), zones_list.end(), std::to_string(zone->GetZoneID()));
|
||||||
|
|
||||||
|
if (it != zones_list.end()) {
|
||||||
const auto& zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ",");
|
const auto& zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ",");
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
for (const auto& result : zones_list) {
|
if (zones_list.size() == zones_limits_list.size()) {
|
||||||
try {
|
try {
|
||||||
if (
|
auto new_limit = std::stoul(zones_limits_list[std::distance(zones_list.begin(), it)]);
|
||||||
std::stoul(result) == zone->GetZoneID() &&
|
|
||||||
std::stoul(zones_limits_list[i]) < bot_spawn_limit
|
|
||||||
) {
|
|
||||||
bot_spawn_limit = std::stoul(zones_limits_list[i]);
|
|
||||||
|
|
||||||
break;
|
if (new_limit < bot_spawn_limit) {
|
||||||
|
bot_spawn_limit = new_limit;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
LogInfo("Invalid entry in Rule Bots:ZoneSpawnLimits: [{}]", e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
++i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (const std::exception& e) {
|
|
||||||
LogInfo("Invalid entry in Rule VegasScaling:SpecialScalingZones or SpecialScalingZonesVersions: [{}]", e.what());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& zones_forced_list = Strings::Split(RuleS(Bots, ZonesWithForcedSpawnLimits), ",");
|
const auto& zones_forced_list = Strings::Split(RuleS(Bots, ZonesWithForcedSpawnLimits), ",");
|
||||||
|
|
||||||
|
if (!zones_forced_list.empty()) {
|
||||||
|
auto it = std::find(zones_forced_list.begin(), zones_forced_list.end(), std::to_string(zone->GetZoneID()));
|
||||||
|
|
||||||
|
if (it != zones_forced_list.end()) {
|
||||||
const auto& zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ",");
|
const auto& zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ",");
|
||||||
i = 0;
|
|
||||||
|
|
||||||
for (const auto& result : zones_forced_list) {
|
if (zones_forced_list.size() == zones_forced_limits_list.size()) {
|
||||||
try {
|
try {
|
||||||
if (
|
auto new_limit = std::stoul(zones_forced_limits_list[std::distance(zones_forced_list.begin(), it)]);
|
||||||
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]);
|
|
||||||
|
|
||||||
break;
|
if (new_limit != bot_spawn_limit) {
|
||||||
|
bot_spawn_limit = new_limit;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
LogInfo("Invalid entry in Rule Bots:ZoneForcedSpawnLimits: [{}]", e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
++i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (const std::exception& e) {
|
|
||||||
LogInfo("Invalid entry in Rule VegasScaling:SpecialScalingZones or SpecialScalingZonesVersions: [{}]", e.what());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +256,8 @@ int Client::GetDefaultBotSettings(uint8 setting_type, uint16 bot_setting) {
|
|||||||
return GetDefaultSpellTypeMinThreshold(bot_setting);
|
return GetDefaultSpellTypeMinThreshold(bot_setting);
|
||||||
case BotSettingCategories::SpellMaxThreshold:
|
case BotSettingCategories::SpellMaxThreshold:
|
||||||
return GetDefaultSpellTypeMaxThreshold(bot_setting);
|
return GetDefaultSpellTypeMaxThreshold(bot_setting);
|
||||||
|
default:
|
||||||
|
return 0; // default return for any unsupported setting type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +271,8 @@ int Client::GetBotSetting(uint8 setting_type, uint16 bot_setting) {
|
|||||||
return GetSpellTypeMinThreshold(bot_setting);
|
return GetSpellTypeMinThreshold(bot_setting);
|
||||||
case BotSettingCategories::SpellMaxThreshold:
|
case BotSettingCategories::SpellMaxThreshold:
|
||||||
return GetSpellTypeMaxThreshold(bot_setting);
|
return GetSpellTypeMaxThreshold(bot_setting);
|
||||||
|
default:
|
||||||
|
return 0; // default return for any unsupported setting type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+42
-14
@@ -73,6 +73,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include "../common/repositories/character_stats_record_repository.h"
|
#include "../common/repositories/character_stats_record_repository.h"
|
||||||
#include "dialogue_window.h"
|
#include "dialogue_window.h"
|
||||||
#include "../common/rulesys.h"
|
#include "../common/rulesys.h"
|
||||||
|
#include "../common/repositories/adventure_members_repository.h"
|
||||||
|
|
||||||
extern QueryServ* QServ;
|
extern QueryServ* QServ;
|
||||||
extern Zone* zone;
|
extern Zone* zone;
|
||||||
@@ -826,6 +827,10 @@ void Client::CompleteConnect()
|
|||||||
if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) {
|
if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) {
|
||||||
InvokeChangePetName(false);
|
InvokeChangePetName(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsNameChangeAllowed() && !RuleB(Character, AlwaysAllowNameChange)) {
|
||||||
|
InvokeChangeNameWindow(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) {
|
if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) {
|
||||||
@@ -909,11 +914,14 @@ void Client::CompleteConnect()
|
|||||||
|
|
||||||
SendDynamicZoneUpdates();
|
SendDynamicZoneUpdates();
|
||||||
|
|
||||||
/** Request adventure info **/
|
// Request adventure info
|
||||||
|
auto members = AdventureMembersRepository::GetWhere(database, fmt::format("charid = {}", CharacterID()));
|
||||||
|
if (!members.empty()) {
|
||||||
auto pack = new ServerPacket(ServerOP_AdventureDataRequest, 64);
|
auto pack = new ServerPacket(ServerOP_AdventureDataRequest, 64);
|
||||||
strcpy((char*)pack->pBuffer, GetName());
|
strcpy((char*)pack->pBuffer, GetName());
|
||||||
worldserver.SendPacket(pack);
|
worldserver.SendPacket(pack);
|
||||||
delete pack;
|
delete pack;
|
||||||
|
}
|
||||||
|
|
||||||
if (IsClient() && CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater) {
|
if (IsClient() && CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater) {
|
||||||
EQApplicationPacket *outapp = MakeBuffsPacket(false);
|
EQApplicationPacket *outapp = MakeBuffsPacket(false);
|
||||||
@@ -4548,14 +4556,14 @@ void Client::Handle_OP_ChangePetName(const EQApplicationPacket *app) {
|
|||||||
|
|
||||||
auto p = (ChangePetName_Struct *) app->pBuffer;
|
auto p = (ChangePetName_Struct *) app->pBuffer;
|
||||||
if (!IsPetNameChangeAllowed()) {
|
if (!IsPetNameChangeAllowed()) {
|
||||||
p->response_code = ChangePetNameResponse::NotEligible;
|
p->response_code = ChangeNameResponse::Ineligible;
|
||||||
QueuePacket(app);
|
QueuePacket(app);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
p->response_code = ChangePetNameResponse::Denied;
|
p->response_code = ChangeNameResponse::Denied;
|
||||||
if (ChangePetName(p->new_pet_name)) {
|
if (ChangePetName(p->new_pet_name)) {
|
||||||
p->response_code = ChangePetNameResponse::Accepted;
|
p->response_code = ChangeNameResponse::Accepted;
|
||||||
}
|
}
|
||||||
|
|
||||||
QueuePacket(app);
|
QueuePacket(app);
|
||||||
@@ -6776,6 +6784,21 @@ void Client::Handle_OP_GMLastName(const EQApplicationPacket *app)
|
|||||||
|
|
||||||
void Client::Handle_OP_GMNameChange(const EQApplicationPacket *app)
|
void Client::Handle_OP_GMNameChange(const EQApplicationPacket *app)
|
||||||
{
|
{
|
||||||
|
if (app->size == sizeof(AltChangeName_Struct)) {
|
||||||
|
auto p = (AltChangeName_Struct *) app->pBuffer;
|
||||||
|
|
||||||
|
if (!IsNameChangeAllowed()) {
|
||||||
|
p->response_code = ChangeNameResponse::Ineligible;
|
||||||
|
QueuePacket(app);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->response_code = ChangeFirstName(p->new_name) ? ChangeNameResponse::Accepted : ChangeNameResponse::Denied;
|
||||||
|
QueuePacket(app);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (app->size != sizeof(GMName_Struct)) {
|
if (app->size != sizeof(GMName_Struct)) {
|
||||||
LogError("Wrong size: OP_GMNameChange, size=[{}], expected [{}]", app->size, sizeof(GMName_Struct));
|
LogError("Wrong size: OP_GMNameChange, size=[{}], expected [{}]", app->size, sizeof(GMName_Struct));
|
||||||
return;
|
return;
|
||||||
@@ -7653,7 +7676,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
|
|||||||
log.char_id = CharacterID();
|
log.char_id = CharacterID();
|
||||||
log.guild_id = GuildID();
|
log.guild_id = GuildID();
|
||||||
log.item_id = inst->GetID();
|
log.item_id = inst->GetID();
|
||||||
|
log.quantity = 1;
|
||||||
|
if (inst->GetCharges() > 0 || inst->IsStackable() || inst->GetItem()->MaxCharges > 0) {
|
||||||
log.quantity = inst->GetCharges();
|
log.quantity = inst->GetCharges();
|
||||||
|
}
|
||||||
|
|
||||||
if (inst->IsAugmented()) {
|
if (inst->IsAugmented()) {
|
||||||
auto augs = inst->GetAugmentIDs();
|
auto augs = inst->GetAugmentIDs();
|
||||||
log.aug_slot_one = augs.at(0);
|
log.aug_slot_one = augs.at(0);
|
||||||
@@ -7737,7 +7764,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
|
|||||||
item.guild_id = GuildID();
|
item.guild_id = GuildID();
|
||||||
item.area = GuildBankDepositArea;
|
item.area = GuildBankDepositArea;
|
||||||
item.item_id = cursor_item->ID;
|
item.item_id = cursor_item->ID;
|
||||||
|
item.quantity = 1;
|
||||||
|
if (cursor_item_inst->GetCharges() > 0 || cursor_item_inst->IsStackable() || cursor_item->MaxCharges > 0) {
|
||||||
item.quantity = cursor_item_inst->GetCharges();
|
item.quantity = cursor_item_inst->GetCharges();
|
||||||
|
}
|
||||||
|
|
||||||
item.donator = GetCleanName();
|
item.donator = GetCleanName();
|
||||||
item.permissions = GuildBankBankerOnly;
|
item.permissions = GuildBankBankerOnly;
|
||||||
if (cursor_item_inst->IsAugmented()) {
|
if (cursor_item_inst->IsAugmented()) {
|
||||||
@@ -7821,12 +7852,9 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inst->GetCharges() > 0) {
|
|
||||||
gbwis->Quantity = inst->GetCharges();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inst->GetCharges() < 0) {
|
|
||||||
gbwis->Quantity = 1;
|
gbwis->Quantity = 1;
|
||||||
|
if (inst->GetCharges() > 0 || inst->IsStackable() || inst->GetItem()->MaxCharges > 0) {
|
||||||
|
gbwis->Quantity = inst->GetCharges();
|
||||||
}
|
}
|
||||||
|
|
||||||
PushItemOnCursor(*inst.get());
|
PushItemOnCursor(*inst.get());
|
||||||
@@ -7956,7 +7984,7 @@ void Client::Handle_OP_GuildCreate(const EQApplicationPacket *app)
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetGuildID(new_guild_id);
|
SetGuildID(new_guild_id);
|
||||||
SendGuildList();
|
UpdateWho();
|
||||||
guild_mgr.MemberAdd(new_guild_id, CharacterID(), GetLevel(), GetClass(), GUILD_LEADER, GetZoneID(), GetName());
|
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.SendGuildRefresh(new_guild_id, true, true, true, true);
|
||||||
guild_mgr.SendToWorldSendGuildList();
|
guild_mgr.SendToWorldSendGuildList();
|
||||||
@@ -8125,7 +8153,7 @@ void Client::Handle_OP_GuildInvite(const EQApplicationPacket *app)
|
|||||||
if (!invitee) {
|
if (!invitee) {
|
||||||
Message(
|
Message(
|
||||||
Chat::Red,
|
Chat::Red,
|
||||||
"Prospective guild member %s must be in zone to preform guild operations on them.",
|
"Prospective guild member %s must be in zone to perform guild operations on them.",
|
||||||
gc->othername
|
gc->othername
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -11038,7 +11066,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
|
|||||||
if (!target)
|
if (!target)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(target) || !CheckLosCheat(target))) {
|
if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(target)) {
|
||||||
mypet->SayString(this, NOT_LEGAL_TARGET);
|
mypet->SayString(this, NOT_LEGAL_TARGET);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -11106,7 +11134,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(GetTarget()) || !CheckLosCheat(GetTarget()))) {
|
if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(GetTarget())) {
|
||||||
mypet->SayString(this, NOT_LEGAL_TARGET);
|
mypet->SayString(this, NOT_LEGAL_TARGET);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -15458,7 +15486,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
|
|||||||
);
|
);
|
||||||
Message(
|
Message(
|
||||||
Chat::Yellow,
|
Chat::Yellow,
|
||||||
"Direct inventory delivey is not yet implemented. Please visit the vendor directly or purchase via parcel delivery."
|
"Direct inventory delivery is not yet implemented. Please visit the vendor directly or purchase via parcel delivery."
|
||||||
);
|
);
|
||||||
in->method = BazaarByDirectToInventory;
|
in->method = BazaarByDirectToInventory;
|
||||||
in->sub_action = Failed;
|
in->sub_action = Failed;
|
||||||
|
|||||||
@@ -217,6 +217,8 @@ bool Client::Process() {
|
|||||||
GetMerc()->Depop();
|
GetMerc()->Depop();
|
||||||
}
|
}
|
||||||
instalog = true;
|
instalog = true;
|
||||||
|
|
||||||
|
camp_timer.Disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsStunned() && stunned_timer.Check())
|
if (IsStunned() && stunned_timer.Check())
|
||||||
|
|||||||
+1
-1
@@ -245,7 +245,7 @@ int command_init(void)
|
|||||||
command_add("zonebootup", "[ZoneServerID] [shortname] - Make a zone server boot a specific zone", AccountStatus::GMLeadAdmin, command_zonebootup) ||
|
command_add("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("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("zoneshard", "[zone] [instance_id] - Teleport explicitly to a zone shard", AccountStatus::Player, command_zone_shard) ||
|
||||||
command_add("zoneshutdown", "[shortname] - Shut down a zone server", AccountStatus::GMLeadAdmin, command_zoneshutdown) ||
|
command_add("zoneshutdown", "[instance|zone] [Instance ID|Zone ID|Zone Short Name] - Shut down a zone server by Instance ID, Zone ID, or Zone Short Name", AccountStatus::GMLeadAdmin, command_zoneshutdown) ||
|
||||||
command_add("zsave", " Saves zheader to the database", AccountStatus::QuestTroupe, command_zsave)
|
command_add("zsave", " Saves zheader to the database", AccountStatus::QuestTroupe, command_zsave)
|
||||||
) {
|
) {
|
||||||
command_deinit();
|
command_deinit();
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ void command_ipban(Client *c, const Seperator *sep);
|
|||||||
void command_kick(Client *c, const Seperator *sep);
|
void command_kick(Client *c, const Seperator *sep);
|
||||||
void command_killallnpcs(Client *c, const Seperator *sep);
|
void command_killallnpcs(Client *c, const Seperator *sep);
|
||||||
void command_kill(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_list(Client *c, const Seperator *sep);
|
||||||
void command_lootsim(Client *c, const Seperator *sep);
|
void command_lootsim(Client *c, const Seperator *sep);
|
||||||
void command_load_shared_memory(Client *c, const Seperator *sep);
|
void command_load_shared_memory(Client *c, const Seperator *sep);
|
||||||
@@ -139,7 +138,6 @@ void command_nudge(Client *c, const Seperator *sep);
|
|||||||
void command_nukebuffs(Client *c, const Seperator *sep);
|
void command_nukebuffs(Client *c, const Seperator *sep);
|
||||||
void command_nukeitem(Client *c, const Seperator *sep);
|
void command_nukeitem(Client *c, const Seperator *sep);
|
||||||
void command_object(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_parcels(Client *c, const Seperator *sep);
|
||||||
void command_path(Client *c, const Seperator *sep);
|
void command_path(Client *c, const Seperator *sep);
|
||||||
void command_peqzone(Client *c, const Seperator *sep);
|
void command_peqzone(Client *c, const Seperator *sep);
|
||||||
@@ -147,7 +145,6 @@ void command_petitems(Client *c, const Seperator *sep);
|
|||||||
void command_picklock(Client *c, const Seperator *sep);
|
void command_picklock(Client *c, const Seperator *sep);
|
||||||
void command_profanity(Client *c, const Seperator *sep);
|
void command_profanity(Client *c, const Seperator *sep);
|
||||||
void command_push(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_raidloot(Client* c, const Seperator* sep);
|
||||||
void command_randomfeatures(Client *c, const Seperator *sep);
|
void command_randomfeatures(Client *c, const Seperator *sep);
|
||||||
void command_refreshgroup(Client *c, const Seperator *sep);
|
void command_refreshgroup(Client *c, const Seperator *sep);
|
||||||
@@ -201,8 +198,6 @@ void command_zone_instance(Client *c, const Seperator *sep);
|
|||||||
void command_zone_shard(Client *c, const Seperator *sep);
|
void command_zone_shard(Client *c, const Seperator *sep);
|
||||||
void command_zonebootup(Client *c, const Seperator *sep);
|
void command_zonebootup(Client *c, const Seperator *sep);
|
||||||
void command_zoneshutdown(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);
|
void command_zsave(Client *c, const Seperator *sep);
|
||||||
|
|
||||||
#include "bot.h"
|
#include "bot.h"
|
||||||
|
|||||||
+2
-2
@@ -290,7 +290,7 @@ Corpse::Corpse(Client *c, int32 rez_exp, KilledByTypes in_killed_by) : Mob(
|
|||||||
m_corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS));
|
m_corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS));
|
||||||
m_loot_cooldown_timer.SetTimer(10);
|
m_loot_cooldown_timer.SetTimer(10);
|
||||||
m_check_rezzable_timer.SetTimer(1000);
|
m_check_rezzable_timer.SetTimer(1000);
|
||||||
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineTime));
|
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineCheckTime) * 1000);
|
||||||
|
|
||||||
m_corpse_rezzable_timer.Disable();
|
m_corpse_rezzable_timer.Disable();
|
||||||
SetRezTimer(true);
|
SetRezTimer(true);
|
||||||
@@ -583,7 +583,7 @@ Corpse::Corpse(
|
|||||||
m_corpse_delay_timer.SetTimer(RuleI(NPC, CorpseUnlockTimer));
|
m_corpse_delay_timer.SetTimer(RuleI(NPC, CorpseUnlockTimer));
|
||||||
m_corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS));
|
m_corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS));
|
||||||
m_loot_cooldown_timer.SetTimer(10);
|
m_loot_cooldown_timer.SetTimer(10);
|
||||||
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineTime));
|
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineCheckTime) * 1000);
|
||||||
m_check_rezzable_timer.SetTimer(1000);
|
m_check_rezzable_timer.SetTimer(1000);
|
||||||
m_corpse_rezzable_timer.Disable();
|
m_corpse_rezzable_timer.Disable();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "data_bucket.h"
|
#include "data_bucket.h"
|
||||||
#include "zonedb.h"
|
#include "zonedb.h"
|
||||||
#include "mob.h"
|
#include "mob.h"
|
||||||
|
#include "client.h"
|
||||||
#include "worldserver.h"
|
#include "worldserver.h"
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -359,7 +360,8 @@ bool DataBucket::GetDataBuckets(Mob *mob)
|
|||||||
BulkLoadEntitiesToCache(DataBucketLoadType::Bot, {id});
|
BulkLoadEntitiesToCache(DataBucketLoadType::Bot, {id});
|
||||||
}
|
}
|
||||||
else if (mob->IsClient()) {
|
else if (mob->IsClient()) {
|
||||||
BulkLoadEntitiesToCache(DataBucketLoadType::Account, {id});
|
uint32 account_id = mob->CastToClient()->AccountID();
|
||||||
|
BulkLoadEntitiesToCache(DataBucketLoadType::Account, {account_id});
|
||||||
BulkLoadEntitiesToCache(DataBucketLoadType::Client, {id});
|
BulkLoadEntitiesToCache(DataBucketLoadType::Client, {id});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,9 @@
|
|||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <glm/ext/matrix_transform.hpp>
|
||||||
|
#include <numbers>
|
||||||
|
|
||||||
#define OPEN_DOOR 0x02
|
#define OPEN_DOOR 0x02
|
||||||
#define CLOSE_DOOR 0x03
|
#define CLOSE_DOOR 0x03
|
||||||
#define OPEN_INVDOOR 0x03
|
#define OPEN_INVDOOR 0x03
|
||||||
@@ -970,3 +973,68 @@ bool Doors::GetIsDoorBlacklisted()
|
|||||||
bool Doors::IsDoorBlacklisted() {
|
bool Doors::IsDoorBlacklisted() {
|
||||||
return m_is_blacklisted_to_open;
|
return m_is_blacklisted_to_open;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Doors::IsDoorBetween(glm::vec4 loc_a, glm::vec4 loc_c, uint16 door_size, float door_depth, bool draw_box) {
|
||||||
|
glm::vec4 door_loc = GetPosition();
|
||||||
|
float half_size = door_size * 0.5f;
|
||||||
|
float half_depth = door_depth * 0.5f;
|
||||||
|
float normalized_heading = std::fmod(door_loc.w, 512.0f);
|
||||||
|
float heading_radians = normalized_heading * (std::numbers::pi / 256.0f);
|
||||||
|
glm::mat4 door_rotation = glm::rotate(glm::mat4(1.0f), -heading_radians, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||||
|
glm::vec3 box_corner_one = glm::vec3(door_size, -half_depth, 0.0f);
|
||||||
|
glm::vec3 box_corner_two = glm::vec3(-door_size, -half_depth, 0.0f);
|
||||||
|
glm::vec3 box_corner_three = glm::vec3(-door_size, half_depth, 0.0f);
|
||||||
|
glm::vec3 box_corner_four = glm::vec3(door_size, half_depth, 0.0f);
|
||||||
|
glm::vec3 door_center_offset = glm::vec3(-(door_size * 0.75f), half_depth * 0.5f, 0.0f);
|
||||||
|
glm::vec3 door_center = glm::vec3(door_loc) + glm::vec3(door_rotation * glm::vec4(door_center_offset, 1.0f));
|
||||||
|
glm::mat4 transform = glm::translate(glm::mat4(1.0f), door_center) * door_rotation;
|
||||||
|
|
||||||
|
box_corner_one = glm::vec3(transform * glm::vec4(box_corner_one, 1.0f));
|
||||||
|
box_corner_two = glm::vec3(transform * glm::vec4(box_corner_two, 1.0f));
|
||||||
|
box_corner_three = glm::vec3(transform * glm::vec4(box_corner_three, 1.0f));
|
||||||
|
box_corner_four = glm::vec3(transform * glm::vec4(box_corner_four, 1.0f));
|
||||||
|
|
||||||
|
if (draw_box) {
|
||||||
|
NPC::SpawnZonePointNodeNPC("loc_a", loc_a);
|
||||||
|
NPC::SpawnZonePointNodeNPC("door_anchor", door_loc);
|
||||||
|
NPC::SpawnZonePointNodeNPC("loc_c", loc_c);
|
||||||
|
NPC::SpawnZonePointNodeNPC("box_corner_one", glm::vec4(box_corner_one.x, box_corner_one.y, box_corner_one.z, 0));
|
||||||
|
NPC::SpawnZonePointNodeNPC("box_corner_two", glm::vec4(box_corner_two.x, box_corner_two.y, box_corner_two.z, 0));
|
||||||
|
NPC::SpawnZonePointNodeNPC("box_corner_three", glm::vec4(box_corner_three.x, box_corner_three.y, box_corner_three.z, 0));
|
||||||
|
NPC::SpawnZonePointNodeNPC("box_corner_four", glm::vec4(box_corner_four.x, box_corner_four.y, box_corner_four.z, 0));
|
||||||
|
NPC::SpawnZonePointNodeNPC("box_center", glm::vec4(door_center.x, door_center.y, door_center.z, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if LoS intersects box
|
||||||
|
auto intersects_box = [](const glm::vec3& a, const glm::vec3& b, const glm::vec3& p1, const glm::vec3& p2) {
|
||||||
|
glm::vec3 ab = b - a;
|
||||||
|
glm::vec3 p1p2 = p2 - p1;
|
||||||
|
|
||||||
|
glm::vec3 cross = glm::cross(ab, p1p2);
|
||||||
|
float cross_magnitude_squared = glm::dot(cross, cross);
|
||||||
|
|
||||||
|
if (cross_magnitude_squared < 1e-6f) {
|
||||||
|
return false; // Lines are parallel or coincident
|
||||||
|
}
|
||||||
|
|
||||||
|
float t = glm::dot(glm::cross(p1 - a, p1p2), cross) / cross_magnitude_squared;
|
||||||
|
float u = glm::dot(glm::cross(p1 - a, ab), cross) / cross_magnitude_squared;
|
||||||
|
|
||||||
|
return (t >= 0.0f && t <= 1.0f && u >= 0.0f && u <= 1.0f);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check intersection with each edge of the door bounding box
|
||||||
|
glm::vec3 loc_a_vec3(loc_a.x, loc_a.y, loc_a.z);
|
||||||
|
glm::vec3 loc_c_vec3(loc_c.x, loc_c.y, loc_c.z);
|
||||||
|
|
||||||
|
if (
|
||||||
|
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_one, box_corner_two) ||
|
||||||
|
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_two, box_corner_three) ||
|
||||||
|
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_three, box_corner_four) ||
|
||||||
|
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_four, box_corner_one)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ public:
|
|||||||
bool IsDestinationZoneSame() const;
|
bool IsDestinationZoneSame() const;
|
||||||
|
|
||||||
bool IsDoorBlacklisted();
|
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; }
|
const char* GetDoorZone() const { return m_zone_name; }
|
||||||
|
|
||||||
|
|||||||
+26
-5
@@ -3143,20 +3143,30 @@ void EntityList::Depop(bool StartSpawnTimer)
|
|||||||
{
|
{
|
||||||
for (auto it = npc_list.begin(); it != npc_list.end(); ++it) {
|
for (auto it = npc_list.begin(); it != npc_list.end(); ++it) {
|
||||||
NPC *pnpc = it->second;
|
NPC *pnpc = it->second;
|
||||||
|
|
||||||
if (pnpc) {
|
if (pnpc) {
|
||||||
Mob *own = pnpc->GetOwner();
|
Mob *own = pnpc->GetOwner();
|
||||||
//do not depop player's pets...
|
//do not depop player/bot pets...
|
||||||
if (own && own->IsClient())
|
if (own && own->IsOfClientBot()) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (pnpc->IsHorse())
|
if (pnpc->IsHorse()) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (pnpc->IsFindable())
|
if (pnpc->IsFindable()) {
|
||||||
UpdateFindableNPCState(pnpc, true);
|
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->WipeHateList();
|
||||||
|
|
||||||
pnpc->Depop(StartSpawnTimer);
|
pnpc->Depop(StartSpawnTimer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5988,3 +5998,14 @@ void EntityList::SendMerchantInventory(Mob* m, int32 slot_id, bool is_delete)
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityList::RestoreCorpse(NPC *npc, uint32_t decay_time)
|
||||||
|
{
|
||||||
|
uint16 corpse_id = npc->GetID();
|
||||||
|
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
||||||
|
auto c = entity_list.GetCorpseByID(corpse_id);
|
||||||
|
if (c) {
|
||||||
|
c->UnLock();
|
||||||
|
c->SetDecayTimer(decay_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -580,6 +580,7 @@ public:
|
|||||||
|
|
||||||
void SendMerchantEnd(Mob* merchant);
|
void SendMerchantEnd(Mob* merchant);
|
||||||
void SendMerchantInventory(Mob* m, int32 slot_id = -1, bool is_delete = false);
|
void SendMerchantInventory(Mob* m, int32 slot_id = -1, bool is_delete = false);
|
||||||
|
void RestoreCorpse(NPC* npc, uint32_t decay_time);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class Zone;
|
friend class Zone;
|
||||||
|
|||||||
@@ -35,6 +35,26 @@ void DoorManipulation::CommandHandler(Client *c, const Seperator *sep)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (arg1 == "drawbox") {
|
||||||
|
Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId());
|
||||||
|
|
||||||
|
if (door) {
|
||||||
|
uint16 door_size = 15;
|
||||||
|
float door_depth = 5.0f;
|
||||||
|
|
||||||
|
if (sep->IsNumber(2) && atof(sep->arg[2]) > 0) {
|
||||||
|
door_size = atof(sep->arg[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sep->IsNumber(3) && atof(sep->arg[3]) > 0) {
|
||||||
|
door_depth = atof(sep->arg[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
door->IsDoorBetween(c->GetPosition(), (c->GetTarget() ? c->GetTarget()->GetPosition() : c->GetPosition()), door_size, door_depth, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// edit menu
|
// edit menu
|
||||||
if (arg1 == "edit") {
|
if (arg1 == "edit") {
|
||||||
Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId());
|
Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId());
|
||||||
@@ -544,6 +564,13 @@ void DoorManipulation::CommandHandler(Client *c, const Seperator *sep)
|
|||||||
c->Message(Chat::White, "#door setinvertstate [0|1] | Sets selected door invert state");
|
c->Message(Chat::White, "#door setinvertstate [0|1] | Sets selected door invert state");
|
||||||
c->Message(Chat::White, "#door setincline <incline> | Sets selected door incline");
|
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, "#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(
|
c->Message(
|
||||||
Chat::White,
|
Chat::White,
|
||||||
fmt::format(
|
fmt::format(
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ void SetName(Client *c, const Seperator *sep)
|
|||||||
std::string new_name = sep->arg[2];
|
std::string new_name = sep->arg[2];
|
||||||
std::string old_name = t->GetCleanName();
|
std::string old_name = t->GetCleanName();
|
||||||
|
|
||||||
if (t->ChangeFirstName(new_name.c_str(), c->GetCleanName())) {
|
if (t->ChangeFirstName(new_name, c->GetCleanName())) {
|
||||||
c->Message(
|
c->Message(
|
||||||
Chat::White,
|
Chat::White,
|
||||||
fmt::format(
|
fmt::format(
|
||||||
@@ -24,17 +24,13 @@ void SetName(Client *c, const Seperator *sep)
|
|||||||
).c_str()
|
).c_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
c->Message(Chat::White, "Sending player to char select.");
|
|
||||||
|
|
||||||
t->Kick("Name was changed");
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
c->Message(
|
c->Message(
|
||||||
Chat::White,
|
Chat::White,
|
||||||
fmt::format(
|
fmt::format(
|
||||||
"Unable to rename {}. Check that the new name '{}' isn't already taken.",
|
"Unable to rename {}. Check that the new name '{}' isn't already taken (Including Pet Names), or isn't invalid",
|
||||||
old_name,
|
old_name,
|
||||||
new_name
|
new_name
|
||||||
).c_str()
|
).c_str()
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
#include "show/zone_loot.cpp"
|
#include "show/zone_loot.cpp"
|
||||||
#include "show/zone_points.cpp"
|
#include "show/zone_points.cpp"
|
||||||
#include "show/zone_status.cpp"
|
#include "show/zone_status.cpp"
|
||||||
|
#include "show/zone_variables.cpp"
|
||||||
|
|
||||||
void command_show(Client *c, const Seperator *sep)
|
void command_show(Client *c, const Seperator *sep)
|
||||||
{
|
{
|
||||||
@@ -110,6 +111,7 @@ void command_show(Client *c, const Seperator *sep)
|
|||||||
Cmd{.cmd = "zone_loot", .u = "zone_loot", .fn = ShowZoneLoot, .a = {"#viewzoneloot"}},
|
Cmd{.cmd = "zone_loot", .u = "zone_loot", .fn = ShowZoneLoot, .a = {"#viewzoneloot"}},
|
||||||
Cmd{.cmd = "zone_points", .u = "zone_points", .fn = ShowZonePoints, .a = {"#showzonepoints"}},
|
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_status", .u = "zone_status", .fn = ShowZoneStatus, .a = {"#zonestatus"}},
|
||||||
|
Cmd{.cmd = "zone_variables", .u = "zone_variables", .fn = ShowZoneVariables},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check for arguments
|
// Check for arguments
|
||||||
|
|||||||
@@ -81,11 +81,6 @@ void ShowZoneData(Client *c, const Seperator *sep)
|
|||||||
DialogueWindow::TableCell(std::to_string(zone->newzone_data.time_type))
|
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(
|
popup_table += DialogueWindow::TableRow(
|
||||||
DialogueWindow::TableCell("Experience Multiplier") +
|
DialogueWindow::TableCell("Experience Multiplier") +
|
||||||
DialogueWindow::TableCell(
|
DialogueWindow::TableCell(
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#include "../../client.h"
|
||||||
|
#include "../../zone.h"
|
||||||
|
|
||||||
|
extern Zone* zone;
|
||||||
|
|
||||||
|
void ShowZoneVariables(Client *c, const Seperator *sep)
|
||||||
|
{
|
||||||
|
if (!zone->GetVariables().empty()) {
|
||||||
|
c->Message(Chat::White, "Zone Variables:");
|
||||||
|
for (auto &key: zone->GetVariables()) {
|
||||||
|
c->Message(Chat::White, fmt::format("{}: {}", key, zone->GetVariable(key)).c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c->Message(Chat::White, "No zone variables set.");
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user