mirror of
https://github.com/EQEmu/Server.git
synced 2026-06-13 06:48:20 +00:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f29478c105 | |||
| 888a88f966 | |||
| 3b617a6652 | |||
| c36c336bc7 | |||
| 4a9779635d | |||
| 20da490bda | |||
| 1221e88d92 | |||
| a2b28b2e16 | |||
| 3d607d352c | |||
| 83cd8119c8 | |||
| 4de8fbbd56 | |||
| 780120036d | |||
| f3697e633c | |||
| 24f8d88333 | |||
| 21bd906a4d | |||
| 138612bc88 | |||
| 5919bb4dea | |||
| 99d249fefd | |||
| c08f286817 | |||
| cd003ff0b7 | |||
| 1a539f6656 | |||
| 7e7fb7b758 | |||
| 5ae87b40e2 | |||
| 0ec07daebb | |||
| 9869da2a0a | |||
| 617eb4432b | |||
| 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 |
+108
@@ -1,3 +1,111 @@
|
|||||||
|
## [23.6.0] 5/14/2025
|
||||||
|
|
||||||
|
### Bots
|
||||||
|
|
||||||
|
* Correct ^pull logic and add checks for Enchanter pets ([#4827](https://github.com/EQEmu/Server/pull/4827)) @nytmyr 2025-05-15
|
||||||
|
* Fix creation limit, spawn limit, level requirement checks ([#4868](https://github.com/EQEmu/Server/pull/4868)) @nytmyr 2025-05-15
|
||||||
|
* Move all spell_id instances to uint16 ([#4876](https://github.com/EQEmu/Server/pull/4876)) @nytmyr 2025-05-15
|
||||||
|
* Prevent non-taunters from potentially fleeing mob on TargetReflection ([#4859](https://github.com/EQEmu/Server/pull/4859)) @nytmyr 2025-04-28
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
* ETL Settings Output ([#4873](https://github.com/EQEmu/Server/pull/4873)) @joligario 2025-05-15
|
||||||
|
|
||||||
|
### Code
|
||||||
|
|
||||||
|
* Fix typo in QueryNameAvailablity ([#4869](https://github.com/EQEmu/Server/pull/4869)) @nytmyr 2025-04-28
|
||||||
|
|
||||||
|
### Crash
|
||||||
|
|
||||||
|
* Fix crash bug with pbae and quest scripts spawning mobs ([#4884](https://github.com/EQEmu/Server/pull/4884)) @carolus21rex 2025-05-15
|
||||||
|
|
||||||
|
### Feature
|
||||||
|
|
||||||
|
* Add Character:TradeskillUpMinChance rule ([#4867](https://github.com/EQEmu/Server/pull/4867)) @zrix-eq 2025-05-15
|
||||||
|
* Enable spawn attribute for NPCTintID ([#4871](https://github.com/EQEmu/Server/pull/4871)) @neckkola 2025-05-15
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* Add trader/buyer cleanup actions ([#4843](https://github.com/EQEmu/Server/pull/4843)) @neckkola 2025-05-15
|
||||||
|
* Fix #copycharacter command ([#4860](https://github.com/EQEmu/Server/pull/4860)) @nytmyr 2025-04-28
|
||||||
|
* Fix Crash with #task ([#4874](https://github.com/EQEmu/Server/pull/4874)) @Kinglykrab 2025-04-30
|
||||||
|
* Fix Object Name Init, User Refs, and Client Sync on Close ([#4861](https://github.com/EQEmu/Server/pull/4861)) @zimp-wow 2025-05-15
|
||||||
|
* Fix breaking change to UF patches caused by Big Bags update ([#4883](https://github.com/EQEmu/Server/pull/4883)) @hbingram 2025-05-15
|
||||||
|
* Prevent Ranged Attack from being triggered at arbitrary rate ([#4879](https://github.com/EQEmu/Server/pull/4879)) @catapultam-habeo 2025-05-15
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
* Store Player Title Sets in Client Memory ([#4836](https://github.com/EQEmu/Server/pull/4836)) @Kinglykrab 2025-05-15
|
||||||
|
|
||||||
|
### Quest API
|
||||||
|
|
||||||
|
* Add Last Login and First Login Flags to EVENT_CONNECT ([#4866](https://github.com/EQEmu/Server/pull/4866)) @Kinglykrab 2025-05-15
|
||||||
|
|
||||||
|
## [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
|
## [23.4.0] 3/30/2025
|
||||||
|
|
||||||
### API
|
### API
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
+4
-4
@@ -1095,11 +1095,11 @@ void Database::SetLFP(uint32 character_id, bool is_lfp)
|
|||||||
CharacterDataRepository::UpdateOne(*this, e);
|
CharacterDataRepository::UpdateOne(*this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon)
|
void Database::SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 ingame)
|
||||||
{
|
{
|
||||||
auto e = CharacterDataRepository::FindOne(*this, character_id);
|
auto e = CharacterDataRepository::FindOne(*this, character_id);
|
||||||
|
|
||||||
e.firstlogon = first_logon;
|
e.ingame = ingame;
|
||||||
e.lfg = is_lfg ? 1 : 0;
|
e.lfg = is_lfg ? 1 : 0;
|
||||||
e.lfp = is_lfp ? 1 : 0;
|
e.lfp = is_lfp ? 1 : 0;
|
||||||
|
|
||||||
@@ -1115,11 +1115,11 @@ void Database::SetLFG(uint32 character_id, bool is_lfg)
|
|||||||
CharacterDataRepository::UpdateOne(*this, e);
|
CharacterDataRepository::UpdateOne(*this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::SetFirstLogon(uint32 character_id, uint8 first_logon)
|
void Database::SetIngame(uint32 character_id, uint8 ingame)
|
||||||
{
|
{
|
||||||
auto e = CharacterDataRepository::FindOne(*this, character_id);
|
auto e = CharacterDataRepository::FindOne(*this, character_id);
|
||||||
|
|
||||||
e.firstlogon = first_logon;
|
e.ingame = ingame;
|
||||||
|
|
||||||
CharacterDataRepository::UpdateOne(*this, e);
|
CharacterDataRepository::UpdateOne(*this, e);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -263,7 +263,7 @@ public:
|
|||||||
bool SaveTime(int8 minute, int8 hour, int8 day, int8 month, int16 year);
|
bool SaveTime(int8 minute, int8 hour, int8 day, int8 month, int16 year);
|
||||||
void ClearMerchantTemp();
|
void ClearMerchantTemp();
|
||||||
void ClearPTimers(uint32 character_id);
|
void ClearPTimers(uint32 character_id);
|
||||||
void SetFirstLogon(uint32 character_id, uint8 first_logon);
|
void SetIngame(uint32 character_id, uint8 ingame);
|
||||||
void SetLFG(uint32 character_id, bool is_lfg);
|
void SetLFG(uint32 character_id, bool is_lfg);
|
||||||
void SetLFP(uint32 character_id, bool is_lfp);
|
void SetLFP(uint32 character_id, bool is_lfp);
|
||||||
void SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon);
|
void SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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`;
|
||||||
@@ -7084,6 +7084,31 @@ 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
|
UPDATE instance_list set expire_at = `start_time` + `duration`; -- backfill existing data
|
||||||
|
|
||||||
CREATE INDEX `idx_expire_at` ON `instance_list` (`expire_at`);
|
CREATE INDEX `idx_expire_at` ON `instance_list` (`expire_at`);
|
||||||
|
)",
|
||||||
|
.content_schema_update = false
|
||||||
|
},
|
||||||
|
ManifestEntry{
|
||||||
|
.version = 9322,
|
||||||
|
.description = "2025_04_24_add_npc_tint_id.sql",
|
||||||
|
.check = "SHOW COLUMNS FROM `npc_types` LIKE 'npc_tint_id'",
|
||||||
|
.condition = "empty",
|
||||||
|
.match = "",
|
||||||
|
.sql = R"(
|
||||||
|
ALTER TABLE `npc_types`
|
||||||
|
ADD COLUMN `npc_tint_id` SMALLINT UNSIGNED NULL DEFAULT '0' AFTER `multiquest_enabled`;
|
||||||
|
)",
|
||||||
|
.content_schema_update = true
|
||||||
|
},
|
||||||
|
ManifestEntry{
|
||||||
|
.version = 9323,
|
||||||
|
.description = "2025_04_16_character_data_first_login.sql",
|
||||||
|
.check = "SHOW COLUMNS FROM `character_data` LIKE 'first_login'",
|
||||||
|
.condition = "empty",
|
||||||
|
.match = "",
|
||||||
|
.sql = R"(
|
||||||
|
ALTER TABLE `character_data`
|
||||||
|
CHANGE COLUMN `firstlogon` `ingame` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `xtargets`,
|
||||||
|
ADD COLUMN `first_login` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `xtargets`;
|
||||||
)",
|
)",
|
||||||
.content_schema_update = false
|
.content_schema_update = false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ namespace DatabaseSchema {
|
|||||||
{"guild_members", "char_id"},
|
{"guild_members", "char_id"},
|
||||||
{"guilds", "id"},
|
{"guilds", "id"},
|
||||||
{"instance_list_player", "id"},
|
{"instance_list_player", "id"},
|
||||||
{"inventory", "charid"},
|
{"inventory", "character_id"},
|
||||||
{"inventory_snapshots", "charid"},
|
{"inventory_snapshots", "charid"},
|
||||||
{"keyring", "char_id"},
|
{"keyring", "char_id"},
|
||||||
{"mail", "charid"},
|
{"mail", "charid"},
|
||||||
|
|||||||
@@ -324,6 +324,8 @@ union
|
|||||||
bool guild_show;
|
bool guild_show;
|
||||||
bool trader;
|
bool trader;
|
||||||
bool buyer;
|
bool buyer;
|
||||||
|
bool untargetable;
|
||||||
|
uint32 npc_tint_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PlayerState_Struct {
|
struct PlayerState_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__);\
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ double EvolvingItemsManager::CalculateProgression(const uint64 current_amount, c
|
|||||||
|
|
||||||
void EvolvingItemsManager::DoLootChecks(const uint32 char_id, const uint16 slot_id, const EQ::ItemInstance &inst) const
|
void EvolvingItemsManager::DoLootChecks(const uint32 char_id, const uint16 slot_id, const EQ::ItemInstance &inst) const
|
||||||
{
|
{
|
||||||
|
if (!inst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
inst.SetEvolveEquipped(false);
|
inst.SetEvolveEquipped(false);
|
||||||
if (inst.IsEvolving() && slot_id <= EQ::invslot::EQUIPMENT_END && slot_id >= EQ::invslot::EQUIPMENT_BEGIN) {
|
if (inst.IsEvolving() && slot_id <= EQ::invslot::EQUIPMENT_END && slot_id >= EQ::invslot::EQUIPMENT_BEGIN) {
|
||||||
inst.SetEvolveEquipped(true);
|
inst.SetEvolveEquipped(true);
|
||||||
@@ -87,6 +91,10 @@ void EvolvingItemsManager::DoLootChecks(const uint32 char_id, const uint16 slot_
|
|||||||
|
|
||||||
uint32 EvolvingItemsManager::GetFinalItemID(const EQ::ItemInstance &inst) const
|
uint32 EvolvingItemsManager::GetFinalItemID(const EQ::ItemInstance &inst) const
|
||||||
{
|
{
|
||||||
|
if (!inst) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const auto start_iterator = std::ranges::find_if(
|
const auto start_iterator = std::ranges::find_if(
|
||||||
evolving_items_manager.GetEvolvingItemsCache().cbegin(),
|
evolving_items_manager.GetEvolvingItemsCache().cbegin(),
|
||||||
evolving_items_manager.GetEvolvingItemsCache().cend(),
|
evolving_items_manager.GetEvolvingItemsCache().cend(),
|
||||||
@@ -116,6 +124,10 @@ uint32 EvolvingItemsManager::GetFinalItemID(const EQ::ItemInstance &inst) const
|
|||||||
|
|
||||||
uint32 EvolvingItemsManager::GetNextEvolveItemID(const EQ::ItemInstance &inst) const
|
uint32 EvolvingItemsManager::GetNextEvolveItemID(const EQ::ItemInstance &inst) const
|
||||||
{
|
{
|
||||||
|
if (!inst) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int8 const current_level = inst.GetEvolveLvl();
|
int8 const current_level = inst.GetEvolveLvl();
|
||||||
|
|
||||||
const auto iterator = std::ranges::find_if(
|
const auto iterator = std::ranges::find_if(
|
||||||
@@ -191,6 +203,10 @@ uint64 EvolvingItemsManager::GetTotalEarnedXP(const EQ::ItemInstance &inst)
|
|||||||
EvolveGetNextItem EvolvingItemsManager::GetNextItemByXP(const EQ::ItemInstance &inst_in, const int64 in_xp)
|
EvolveGetNextItem EvolvingItemsManager::GetNextItemByXP(const EQ::ItemInstance &inst_in, const int64 in_xp)
|
||||||
{
|
{
|
||||||
EvolveGetNextItem ets{};
|
EvolveGetNextItem ets{};
|
||||||
|
if (!inst_in) {
|
||||||
|
return ets;
|
||||||
|
}
|
||||||
|
|
||||||
const auto evolve_items = GetEvolveIDItems(inst_in.GetEvolveLoreID());
|
const auto evolve_items = GetEvolveIDItems(inst_in.GetEvolveLoreID());
|
||||||
uint32 max_transfer_level = 0;
|
uint32 max_transfer_level = 0;
|
||||||
int64 xp = in_xp;
|
int64 xp = in_xp;
|
||||||
@@ -235,6 +251,9 @@ EvolveTransfer EvolvingItemsManager::DetermineTransferResults(
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
EvolveTransfer ets{};
|
EvolveTransfer ets{};
|
||||||
|
if (!inst_from || !inst_to) {
|
||||||
|
return ets;
|
||||||
|
}
|
||||||
|
|
||||||
auto evolving_details_inst_from = evolving_items_manager.GetEvolveItemDetails(inst_from.GetID());
|
auto evolving_details_inst_from = evolving_items_manager.GetEvolveItemDetails(inst_from.GetID());
|
||||||
auto evolving_details_inst_to = evolving_items_manager.GetEvolveItemDetails(inst_to.GetID());
|
auto evolving_details_inst_to = evolving_items_manager.GetEvolveItemDetails(inst_to.GetID());
|
||||||
@@ -295,6 +314,10 @@ uint32 EvolvingItemsManager::GetFirstItemInLoreGroupByItemID(const uint32 item_i
|
|||||||
|
|
||||||
void EvolvingItemsManager::LoadPlayerEvent(const EQ::ItemInstance &inst, PlayerEvent::EvolveItem &e)
|
void EvolvingItemsManager::LoadPlayerEvent(const EQ::ItemInstance &inst, PlayerEvent::EvolveItem &e)
|
||||||
{
|
{
|
||||||
|
if (!inst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e.item_id = inst.GetID();
|
e.item_id = inst.GetID();
|
||||||
e.item_name = inst.GetItem() ? inst.GetItem()->Name : std::string();
|
e.item_name = inst.GetItem() ? inst.GetItem()->Name : std::string();
|
||||||
e.level = inst.GetEvolveLvl();
|
e.level = inst.GetEvolveLvl();
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ public:
|
|||||||
ItemsEvolvingDetailsRepository::ItemsEvolvingDetails GetEvolveItemDetails(uint64 id);
|
ItemsEvolvingDetailsRepository::ItemsEvolvingDetails GetEvolveItemDetails(uint64 id);
|
||||||
EvolveTransfer DetermineTransferResults(const EQ::ItemInstance& inst_from, const EQ::ItemInstance& inst_to);
|
EvolveTransfer DetermineTransferResults(const EQ::ItemInstance& inst_from, const EQ::ItemInstance& inst_to);
|
||||||
EvolveGetNextItem GetNextItemByXP(const EQ::ItemInstance &inst_in, int64 in_xp);
|
EvolveGetNextItem GetNextItemByXP(const EQ::ItemInstance &inst_in, int64 in_xp);
|
||||||
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails>& GetEvolvingItemsCache() { return evolving_items_cache; }
|
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails>& GetEvolvingItemsCache() { return m_evolving_items_cache; }
|
||||||
std::vector<ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> GetEvolveIDItems(uint32 evolve_id);
|
std::vector<ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> GetEvolveIDItems(uint32 evolve_id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> evolving_items_cache;
|
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> m_evolving_items_cache;
|
||||||
Database * m_db;
|
Database * m_db;
|
||||||
Database * m_content_db;
|
Database * m_content_db;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -4688,7 +4688,7 @@ namespace RoF2
|
|||||||
Bitfields->linkdead = 0;
|
Bitfields->linkdead = 0;
|
||||||
Bitfields->showhelm = emu->showhelm;
|
Bitfields->showhelm = emu->showhelm;
|
||||||
Bitfields->trader = emu->trader ? 1 : 0;
|
Bitfields->trader = emu->trader ? 1 : 0;
|
||||||
Bitfields->targetable = 1;
|
Bitfields->targetable = emu->NPC ? emu->untargetable : 1;
|
||||||
Bitfields->targetable_with_hotkey = emu->targetable_with_hotkey ? 1 : 0;
|
Bitfields->targetable_with_hotkey = emu->targetable_with_hotkey ? 1 : 0;
|
||||||
Bitfields->showname = ShowName;
|
Bitfields->showname = ShowName;
|
||||||
|
|
||||||
@@ -4841,7 +4841,7 @@ namespace RoF2
|
|||||||
|
|
||||||
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // FindBits MQ2 name
|
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // FindBits MQ2 name
|
||||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->PlayerState);
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->PlayerState);
|
||||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // NpcTintIndex
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->npc_tint_id); // NpcTintIndex
|
||||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex
|
||||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // SecondaryTintIndex
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // SecondaryTintIndex
|
||||||
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // These do something with OP_WeaponEquip1
|
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // These do something with OP_WeaponEquip1
|
||||||
|
|||||||
+11
-11
@@ -4908,12 +4908,12 @@ namespace UF
|
|||||||
UFSlot = serverSlot - 2;
|
UFSlot = serverSlot - 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (serverSlot <= EQ::invbag::GENERAL_BAGS_8_END && serverSlot >= EQ::invbag::GENERAL_BAGS_BEGIN) {
|
else if (serverSlot <= EQ::invbag::GENERAL_BAGS_END && serverSlot >= EQ::invbag::GENERAL_BAGS_BEGIN) {
|
||||||
UFSlot = serverSlot + 11;
|
UFSlot = serverSlot - (EQ::invbag::GENERAL_BAGS_BEGIN - invbag::GENERAL_BAGS_BEGIN)/*3748*/ - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); // + 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (serverSlot <= EQ::invbag::CURSOR_BAG_END && serverSlot >= EQ::invbag::CURSOR_BAG_BEGIN) {
|
else if (serverSlot <= EQ::invbag::CURSOR_BAG_END && serverSlot >= EQ::invbag::CURSOR_BAG_BEGIN) {
|
||||||
UFSlot = serverSlot - 9;
|
UFSlot = serverSlot - (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN)/*5668*/; // - 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (serverSlot <= EQ::invslot::TRIBUTE_END && serverSlot >= EQ::invslot::TRIBUTE_BEGIN) {
|
else if (serverSlot <= EQ::invslot::TRIBUTE_END && serverSlot >= EQ::invslot::TRIBUTE_BEGIN) {
|
||||||
@@ -4933,7 +4933,7 @@ namespace UF
|
|||||||
}
|
}
|
||||||
|
|
||||||
else if (serverSlot <= EQ::invbag::BANK_BAGS_END && serverSlot >= EQ::invbag::BANK_BAGS_BEGIN) {
|
else if (serverSlot <= EQ::invbag::BANK_BAGS_END && serverSlot >= EQ::invbag::BANK_BAGS_BEGIN) {
|
||||||
UFSlot = serverSlot + 1;
|
UFSlot = serverSlot - (EQ::invbag::BANK_BAGS_BEGIN - invbag::BANK_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); // + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (serverSlot <= EQ::invslot::SHARED_BANK_END && serverSlot >= EQ::invslot::SHARED_BANK_BEGIN) {
|
else if (serverSlot <= EQ::invslot::SHARED_BANK_END && serverSlot >= EQ::invslot::SHARED_BANK_BEGIN) {
|
||||||
@@ -4941,7 +4941,7 @@ namespace UF
|
|||||||
}
|
}
|
||||||
|
|
||||||
else if (serverSlot <= EQ::invbag::SHARED_BANK_BAGS_END && serverSlot >= EQ::invbag::SHARED_BANK_BAGS_BEGIN) {
|
else if (serverSlot <= EQ::invbag::SHARED_BANK_BAGS_END && serverSlot >= EQ::invbag::SHARED_BANK_BAGS_BEGIN) {
|
||||||
UFSlot = serverSlot + 1;
|
UFSlot = serverSlot - (EQ::invbag::SHARED_BANK_BAGS_BEGIN - invbag::SHARED_BANK_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::SHARED_BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); // + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (serverSlot <= EQ::invslot::TRADE_END && serverSlot >= EQ::invslot::TRADE_BEGIN) {
|
else if (serverSlot <= EQ::invslot::TRADE_END && serverSlot >= EQ::invslot::TRADE_BEGIN) {
|
||||||
@@ -4949,7 +4949,7 @@ namespace UF
|
|||||||
}
|
}
|
||||||
|
|
||||||
else if (serverSlot <= EQ::invbag::TRADE_BAGS_END && serverSlot >= EQ::invbag::TRADE_BAGS_BEGIN) {
|
else if (serverSlot <= EQ::invbag::TRADE_BAGS_END && serverSlot >= EQ::invbag::TRADE_BAGS_BEGIN) {
|
||||||
UFSlot = serverSlot;
|
UFSlot = serverSlot - (EQ::invbag::TRADE_BAGS_BEGIN - invbag::TRADE_BAGS_BEGIN) - ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((serverSlot - EQ::invbag::TRADE_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT)); // + 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (serverSlot <= EQ::invslot::WORLD_END && serverSlot >= EQ::invslot::WORLD_BEGIN) {
|
else if (serverSlot <= EQ::invslot::WORLD_END && serverSlot >= EQ::invslot::WORLD_BEGIN) {
|
||||||
@@ -4991,11 +4991,11 @@ namespace UF
|
|||||||
}
|
}
|
||||||
|
|
||||||
else if (ufSlot <= invbag::GENERAL_BAGS_END && ufSlot >= invbag::GENERAL_BAGS_BEGIN) {
|
else if (ufSlot <= invbag::GENERAL_BAGS_END && ufSlot >= invbag::GENERAL_BAGS_BEGIN) {
|
||||||
ServerSlot = ufSlot - 11;
|
ServerSlot = ufSlot + (EQ::invbag::GENERAL_BAGS_BEGIN - invbag::GENERAL_BAGS_BEGIN)/*3748*/ + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((ufSlot - invbag::GENERAL_BAGS_BEGIN) / invbag::SLOT_COUNT)); // - 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (ufSlot <= invbag::CURSOR_BAG_END && ufSlot >= invbag::CURSOR_BAG_BEGIN) {
|
else if (ufSlot <= invbag::CURSOR_BAG_END && ufSlot >= invbag::CURSOR_BAG_BEGIN) {
|
||||||
ServerSlot = ufSlot + 9;
|
ServerSlot = ufSlot + (EQ::invbag::CURSOR_BAG_BEGIN - invbag::CURSOR_BAG_BEGIN)/*5668*/; // + 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (ufSlot <= invslot::TRIBUTE_END && ufSlot >= invslot::TRIBUTE_BEGIN) {
|
else if (ufSlot <= invslot::TRIBUTE_END && ufSlot >= invslot::TRIBUTE_BEGIN) {
|
||||||
@@ -5015,7 +5015,7 @@ namespace UF
|
|||||||
}
|
}
|
||||||
|
|
||||||
else if (ufSlot <= invbag::BANK_BAGS_END && ufSlot >= invbag::BANK_BAGS_BEGIN) {
|
else if (ufSlot <= invbag::BANK_BAGS_END && ufSlot >= invbag::BANK_BAGS_BEGIN) {
|
||||||
ServerSlot = ufSlot - 1;
|
ServerSlot = ufSlot + (EQ::invbag::BANK_BAGS_BEGIN - invbag::BANK_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((ufSlot - invbag::BANK_BAGS_BEGIN) / invbag::SLOT_COUNT)); // - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (ufSlot <= invslot::SHARED_BANK_END && ufSlot >= invslot::SHARED_BANK_BEGIN) {
|
else if (ufSlot <= invslot::SHARED_BANK_END && ufSlot >= invslot::SHARED_BANK_BEGIN) {
|
||||||
@@ -5023,7 +5023,7 @@ namespace UF
|
|||||||
}
|
}
|
||||||
|
|
||||||
else if (ufSlot <= invbag::SHARED_BANK_BAGS_END && ufSlot >= invbag::SHARED_BANK_BAGS_BEGIN) {
|
else if (ufSlot <= invbag::SHARED_BANK_BAGS_END && ufSlot >= invbag::SHARED_BANK_BAGS_BEGIN) {
|
||||||
ServerSlot = ufSlot - 1;
|
ServerSlot = ufSlot + (EQ::invbag::SHARED_BANK_BAGS_BEGIN - invbag::SHARED_BANK_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((ufSlot - invbag::SHARED_BANK_BAGS_BEGIN) / invbag::SLOT_COUNT)); // - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (ufSlot <= invslot::TRADE_END && ufSlot >= invslot::TRADE_BEGIN) {
|
else if (ufSlot <= invslot::TRADE_END && ufSlot >= invslot::TRADE_BEGIN) {
|
||||||
@@ -5031,7 +5031,7 @@ namespace UF
|
|||||||
}
|
}
|
||||||
|
|
||||||
else if (ufSlot <= invbag::TRADE_BAGS_END && ufSlot >= invbag::TRADE_BAGS_BEGIN) {
|
else if (ufSlot <= invbag::TRADE_BAGS_END && ufSlot >= invbag::TRADE_BAGS_BEGIN) {
|
||||||
ServerSlot = ufSlot;
|
ServerSlot = ufSlot + (EQ::invbag::TRADE_BAGS_BEGIN - invbag::TRADE_BAGS_BEGIN) + ((EQ::invbag::SLOT_COUNT - invbag::SLOT_COUNT) * ((ufSlot - invbag::TRADE_BAGS_BEGIN) / invbag::SLOT_COUNT)); // - 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (ufSlot <= invslot::WORLD_END && ufSlot >= invslot::WORLD_BEGIN) {
|
else if (ufSlot <= invslot::WORLD_END && ufSlot >= invslot::WORLD_BEGIN) {
|
||||||
|
|||||||
@@ -115,7 +115,8 @@ public:
|
|||||||
uint8_t lfg;
|
uint8_t lfg;
|
||||||
std::string mailkey;
|
std::string mailkey;
|
||||||
uint8_t xtargets;
|
uint8_t xtargets;
|
||||||
int8_t firstlogon;
|
uint8_t ingame;
|
||||||
|
uint32_t first_login;
|
||||||
uint32_t e_aa_effects;
|
uint32_t e_aa_effects;
|
||||||
uint32_t e_percent_to_aa;
|
uint32_t e_percent_to_aa;
|
||||||
uint32_t e_expended_aa_spent;
|
uint32_t e_expended_aa_spent;
|
||||||
@@ -230,7 +231,8 @@ public:
|
|||||||
"lfg",
|
"lfg",
|
||||||
"mailkey",
|
"mailkey",
|
||||||
"xtargets",
|
"xtargets",
|
||||||
"firstlogon",
|
"ingame",
|
||||||
|
"first_login",
|
||||||
"e_aa_effects",
|
"e_aa_effects",
|
||||||
"e_percent_to_aa",
|
"e_percent_to_aa",
|
||||||
"e_expended_aa_spent",
|
"e_expended_aa_spent",
|
||||||
@@ -341,7 +343,8 @@ public:
|
|||||||
"lfg",
|
"lfg",
|
||||||
"mailkey",
|
"mailkey",
|
||||||
"xtargets",
|
"xtargets",
|
||||||
"firstlogon",
|
"ingame",
|
||||||
|
"first_login",
|
||||||
"e_aa_effects",
|
"e_aa_effects",
|
||||||
"e_percent_to_aa",
|
"e_percent_to_aa",
|
||||||
"e_expended_aa_spent",
|
"e_expended_aa_spent",
|
||||||
@@ -486,7 +489,8 @@ public:
|
|||||||
e.lfg = 0;
|
e.lfg = 0;
|
||||||
e.mailkey = "";
|
e.mailkey = "";
|
||||||
e.xtargets = 5;
|
e.xtargets = 5;
|
||||||
e.firstlogon = 0;
|
e.ingame = 0;
|
||||||
|
e.first_login = 0;
|
||||||
e.e_aa_effects = 0;
|
e.e_aa_effects = 0;
|
||||||
e.e_percent_to_aa = 0;
|
e.e_percent_to_aa = 0;
|
||||||
e.e_expended_aa_spent = 0;
|
e.e_expended_aa_spent = 0;
|
||||||
@@ -627,15 +631,16 @@ public:
|
|||||||
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
|
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
|
||||||
e.mailkey = row[94] ? row[94] : "";
|
e.mailkey = row[94] ? row[94] : "";
|
||||||
e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5;
|
e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5;
|
||||||
e.firstlogon = row[96] ? static_cast<int8_t>(atoi(row[96])) : 0;
|
e.ingame = row[96] ? static_cast<uint8_t>(strtoul(row[96], nullptr, 10)) : 0;
|
||||||
e.e_aa_effects = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
e.first_login = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||||
e.e_percent_to_aa = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
e.e_aa_effects = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||||
e.e_expended_aa_spent = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
e.e_percent_to_aa = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||||
e.aa_points_spent_old = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
e.e_expended_aa_spent = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||||
e.aa_points_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
e.aa_points_spent_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||||
e.e_last_invsnapshot = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
e.aa_points_old = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||||
e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10);
|
e.e_last_invsnapshot = row[103] ? static_cast<uint32_t>(strtoul(row[103], nullptr, 10)) : 0;
|
||||||
e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
|
e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10);
|
||||||
|
e.illusion_block = row[105] ? static_cast<uint8_t>(strtoul(row[105], nullptr, 10)) : 0;
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -764,15 +769,16 @@ public:
|
|||||||
v.push_back(columns[93] + " = " + std::to_string(e.lfg));
|
v.push_back(columns[93] + " = " + std::to_string(e.lfg));
|
||||||
v.push_back(columns[94] + " = '" + Strings::Escape(e.mailkey) + "'");
|
v.push_back(columns[94] + " = '" + Strings::Escape(e.mailkey) + "'");
|
||||||
v.push_back(columns[95] + " = " + std::to_string(e.xtargets));
|
v.push_back(columns[95] + " = " + std::to_string(e.xtargets));
|
||||||
v.push_back(columns[96] + " = " + std::to_string(e.firstlogon));
|
v.push_back(columns[96] + " = " + std::to_string(e.ingame));
|
||||||
v.push_back(columns[97] + " = " + std::to_string(e.e_aa_effects));
|
v.push_back(columns[97] + " = " + std::to_string(e.first_login));
|
||||||
v.push_back(columns[98] + " = " + std::to_string(e.e_percent_to_aa));
|
v.push_back(columns[98] + " = " + std::to_string(e.e_aa_effects));
|
||||||
v.push_back(columns[99] + " = " + std::to_string(e.e_expended_aa_spent));
|
v.push_back(columns[99] + " = " + std::to_string(e.e_percent_to_aa));
|
||||||
v.push_back(columns[100] + " = " + std::to_string(e.aa_points_spent_old));
|
v.push_back(columns[100] + " = " + std::to_string(e.e_expended_aa_spent));
|
||||||
v.push_back(columns[101] + " = " + std::to_string(e.aa_points_old));
|
v.push_back(columns[101] + " = " + std::to_string(e.aa_points_spent_old));
|
||||||
v.push_back(columns[102] + " = " + std::to_string(e.e_last_invsnapshot));
|
v.push_back(columns[102] + " = " + std::to_string(e.aa_points_old));
|
||||||
v.push_back(columns[103] + " = FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")");
|
v.push_back(columns[103] + " = " + std::to_string(e.e_last_invsnapshot));
|
||||||
v.push_back(columns[104] + " = " + std::to_string(e.illusion_block));
|
v.push_back(columns[104] + " = FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")");
|
||||||
|
v.push_back(columns[105] + " = " + std::to_string(e.illusion_block));
|
||||||
|
|
||||||
auto results = db.QueryDatabase(
|
auto results = db.QueryDatabase(
|
||||||
fmt::format(
|
fmt::format(
|
||||||
@@ -890,7 +896,8 @@ public:
|
|||||||
v.push_back(std::to_string(e.lfg));
|
v.push_back(std::to_string(e.lfg));
|
||||||
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
||||||
v.push_back(std::to_string(e.xtargets));
|
v.push_back(std::to_string(e.xtargets));
|
||||||
v.push_back(std::to_string(e.firstlogon));
|
v.push_back(std::to_string(e.ingame));
|
||||||
|
v.push_back(std::to_string(e.first_login));
|
||||||
v.push_back(std::to_string(e.e_aa_effects));
|
v.push_back(std::to_string(e.e_aa_effects));
|
||||||
v.push_back(std::to_string(e.e_percent_to_aa));
|
v.push_back(std::to_string(e.e_percent_to_aa));
|
||||||
v.push_back(std::to_string(e.e_expended_aa_spent));
|
v.push_back(std::to_string(e.e_expended_aa_spent));
|
||||||
@@ -1024,7 +1031,8 @@ public:
|
|||||||
v.push_back(std::to_string(e.lfg));
|
v.push_back(std::to_string(e.lfg));
|
||||||
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
||||||
v.push_back(std::to_string(e.xtargets));
|
v.push_back(std::to_string(e.xtargets));
|
||||||
v.push_back(std::to_string(e.firstlogon));
|
v.push_back(std::to_string(e.ingame));
|
||||||
|
v.push_back(std::to_string(e.first_login));
|
||||||
v.push_back(std::to_string(e.e_aa_effects));
|
v.push_back(std::to_string(e.e_aa_effects));
|
||||||
v.push_back(std::to_string(e.e_percent_to_aa));
|
v.push_back(std::to_string(e.e_percent_to_aa));
|
||||||
v.push_back(std::to_string(e.e_expended_aa_spent));
|
v.push_back(std::to_string(e.e_expended_aa_spent));
|
||||||
@@ -1162,15 +1170,16 @@ public:
|
|||||||
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
|
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
|
||||||
e.mailkey = row[94] ? row[94] : "";
|
e.mailkey = row[94] ? row[94] : "";
|
||||||
e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5;
|
e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5;
|
||||||
e.firstlogon = row[96] ? static_cast<int8_t>(atoi(row[96])) : 0;
|
e.ingame = row[96] ? static_cast<uint8_t>(strtoul(row[96], nullptr, 10)) : 0;
|
||||||
e.e_aa_effects = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
e.first_login = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||||
e.e_percent_to_aa = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
e.e_aa_effects = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||||
e.e_expended_aa_spent = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
e.e_percent_to_aa = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||||
e.aa_points_spent_old = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
e.e_expended_aa_spent = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||||
e.aa_points_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
e.aa_points_spent_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||||
e.e_last_invsnapshot = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
e.aa_points_old = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||||
e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10);
|
e.e_last_invsnapshot = row[103] ? static_cast<uint32_t>(strtoul(row[103], nullptr, 10)) : 0;
|
||||||
e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
|
e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10);
|
||||||
|
e.illusion_block = row[105] ? static_cast<uint8_t>(strtoul(row[105], nullptr, 10)) : 0;
|
||||||
|
|
||||||
all_entries.push_back(e);
|
all_entries.push_back(e);
|
||||||
}
|
}
|
||||||
@@ -1291,15 +1300,16 @@ public:
|
|||||||
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
|
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
|
||||||
e.mailkey = row[94] ? row[94] : "";
|
e.mailkey = row[94] ? row[94] : "";
|
||||||
e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5;
|
e.xtargets = row[95] ? static_cast<uint8_t>(strtoul(row[95], nullptr, 10)) : 5;
|
||||||
e.firstlogon = row[96] ? static_cast<int8_t>(atoi(row[96])) : 0;
|
e.ingame = row[96] ? static_cast<uint8_t>(strtoul(row[96], nullptr, 10)) : 0;
|
||||||
e.e_aa_effects = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
e.first_login = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
|
||||||
e.e_percent_to_aa = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
e.e_aa_effects = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
|
||||||
e.e_expended_aa_spent = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
e.e_percent_to_aa = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
|
||||||
e.aa_points_spent_old = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
e.e_expended_aa_spent = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
|
||||||
e.aa_points_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
e.aa_points_spent_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
|
||||||
e.e_last_invsnapshot = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
e.aa_points_old = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
|
||||||
e.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10);
|
e.e_last_invsnapshot = row[103] ? static_cast<uint32_t>(strtoul(row[103], nullptr, 10)) : 0;
|
||||||
e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
|
e.deleted_at = strtoll(row[104] ? row[104] : "-1", nullptr, 10);
|
||||||
|
e.illusion_block = row[105] ? static_cast<uint8_t>(strtoul(row[105], nullptr, 10)) : 0;
|
||||||
|
|
||||||
all_entries.push_back(e);
|
all_entries.push_back(e);
|
||||||
}
|
}
|
||||||
@@ -1470,7 +1480,8 @@ public:
|
|||||||
v.push_back(std::to_string(e.lfg));
|
v.push_back(std::to_string(e.lfg));
|
||||||
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
||||||
v.push_back(std::to_string(e.xtargets));
|
v.push_back(std::to_string(e.xtargets));
|
||||||
v.push_back(std::to_string(e.firstlogon));
|
v.push_back(std::to_string(e.ingame));
|
||||||
|
v.push_back(std::to_string(e.first_login));
|
||||||
v.push_back(std::to_string(e.e_aa_effects));
|
v.push_back(std::to_string(e.e_aa_effects));
|
||||||
v.push_back(std::to_string(e.e_percent_to_aa));
|
v.push_back(std::to_string(e.e_percent_to_aa));
|
||||||
v.push_back(std::to_string(e.e_expended_aa_spent));
|
v.push_back(std::to_string(e.e_expended_aa_spent));
|
||||||
@@ -1597,7 +1608,8 @@ public:
|
|||||||
v.push_back(std::to_string(e.lfg));
|
v.push_back(std::to_string(e.lfg));
|
||||||
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
|
||||||
v.push_back(std::to_string(e.xtargets));
|
v.push_back(std::to_string(e.xtargets));
|
||||||
v.push_back(std::to_string(e.firstlogon));
|
v.push_back(std::to_string(e.ingame));
|
||||||
|
v.push_back(std::to_string(e.first_login));
|
||||||
v.push_back(std::to_string(e.e_aa_effects));
|
v.push_back(std::to_string(e.e_aa_effects));
|
||||||
v.push_back(std::to_string(e.e_percent_to_aa));
|
v.push_back(std::to_string(e.e_percent_to_aa));
|
||||||
v.push_back(std::to_string(e.e_expended_aa_spent));
|
v.push_back(std::to_string(e.e_expended_aa_spent));
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ public:
|
|||||||
uint8_t keeps_sold_items;
|
uint8_t keeps_sold_items;
|
||||||
uint8_t is_parcel_merchant;
|
uint8_t is_parcel_merchant;
|
||||||
uint8_t multiquest_enabled;
|
uint8_t multiquest_enabled;
|
||||||
|
uint16_t npc_tint_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::string PrimaryKey()
|
static std::string PrimaryKey()
|
||||||
@@ -289,6 +290,7 @@ public:
|
|||||||
"keeps_sold_items",
|
"keeps_sold_items",
|
||||||
"is_parcel_merchant",
|
"is_parcel_merchant",
|
||||||
"multiquest_enabled",
|
"multiquest_enabled",
|
||||||
|
"npc_tint_id",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,6 +427,7 @@ public:
|
|||||||
"keeps_sold_items",
|
"keeps_sold_items",
|
||||||
"is_parcel_merchant",
|
"is_parcel_merchant",
|
||||||
"multiquest_enabled",
|
"multiquest_enabled",
|
||||||
|
"npc_tint_id",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,6 +598,7 @@ public:
|
|||||||
e.keeps_sold_items = 1;
|
e.keeps_sold_items = 1;
|
||||||
e.is_parcel_merchant = 0;
|
e.is_parcel_merchant = 0;
|
||||||
e.multiquest_enabled = 0;
|
e.multiquest_enabled = 0;
|
||||||
|
e.npc_tint_id = 0;
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -761,6 +765,7 @@ public:
|
|||||||
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
||||||
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
||||||
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
||||||
|
e.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -923,6 +928,7 @@ public:
|
|||||||
v.push_back(columns[127] + " = " + std::to_string(e.keeps_sold_items));
|
v.push_back(columns[127] + " = " + std::to_string(e.keeps_sold_items));
|
||||||
v.push_back(columns[128] + " = " + std::to_string(e.is_parcel_merchant));
|
v.push_back(columns[128] + " = " + std::to_string(e.is_parcel_merchant));
|
||||||
v.push_back(columns[129] + " = " + std::to_string(e.multiquest_enabled));
|
v.push_back(columns[129] + " = " + std::to_string(e.multiquest_enabled));
|
||||||
|
v.push_back(columns[130] + " = " + std::to_string(e.npc_tint_id));
|
||||||
|
|
||||||
auto results = db.QueryDatabase(
|
auto results = db.QueryDatabase(
|
||||||
fmt::format(
|
fmt::format(
|
||||||
@@ -1074,6 +1080,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.keeps_sold_items));
|
v.push_back(std::to_string(e.keeps_sold_items));
|
||||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||||
v.push_back(std::to_string(e.multiquest_enabled));
|
v.push_back(std::to_string(e.multiquest_enabled));
|
||||||
|
v.push_back(std::to_string(e.npc_tint_id));
|
||||||
|
|
||||||
auto results = db.QueryDatabase(
|
auto results = db.QueryDatabase(
|
||||||
fmt::format(
|
fmt::format(
|
||||||
@@ -1233,6 +1240,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.keeps_sold_items));
|
v.push_back(std::to_string(e.keeps_sold_items));
|
||||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||||
v.push_back(std::to_string(e.multiquest_enabled));
|
v.push_back(std::to_string(e.multiquest_enabled));
|
||||||
|
v.push_back(std::to_string(e.npc_tint_id));
|
||||||
|
|
||||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||||
}
|
}
|
||||||
@@ -1396,6 +1404,7 @@ public:
|
|||||||
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
||||||
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
||||||
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
||||||
|
e.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
|
||||||
|
|
||||||
all_entries.push_back(e);
|
all_entries.push_back(e);
|
||||||
}
|
}
|
||||||
@@ -1550,6 +1559,7 @@ public:
|
|||||||
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
||||||
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
||||||
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
||||||
|
e.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
|
||||||
|
|
||||||
all_entries.push_back(e);
|
all_entries.push_back(e);
|
||||||
}
|
}
|
||||||
@@ -1754,6 +1764,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.keeps_sold_items));
|
v.push_back(std::to_string(e.keeps_sold_items));
|
||||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||||
v.push_back(std::to_string(e.multiquest_enabled));
|
v.push_back(std::to_string(e.multiquest_enabled));
|
||||||
|
v.push_back(std::to_string(e.npc_tint_id));
|
||||||
|
|
||||||
auto results = db.QueryDatabase(
|
auto results = db.QueryDatabase(
|
||||||
fmt::format(
|
fmt::format(
|
||||||
@@ -1906,6 +1917,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.keeps_sold_items));
|
v.push_back(std::to_string(e.keeps_sold_items));
|
||||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||||
v.push_back(std::to_string(e.multiquest_enabled));
|
v.push_back(std::to_string(e.multiquest_enabled));
|
||||||
|
v.push_back(std::to_string(e.npc_tint_id));
|
||||||
|
|
||||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,13 +106,8 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto buy_lines = BaseBuyerBuyLinesRepository::GetWhere(
|
auto buy_lines =
|
||||||
db,
|
BaseBuyerBuyLinesRepository::GetWhere(db, fmt::format("`buyer_id` = {}", buyer.front().id));
|
||||||
fmt::format("`buyer_id` = '{}'", buyer.front().id)
|
|
||||||
);
|
|
||||||
if (buy_lines.empty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> buy_line_ids{};
|
std::vector<std::string> buy_line_ids{};
|
||||||
for (auto const &bl: buy_lines) {
|
for (auto const &bl: buy_lines) {
|
||||||
@@ -121,20 +116,62 @@ public:
|
|||||||
|
|
||||||
DeleteWhere(db, fmt::format("`char_id` = '{}';", char_id));
|
DeleteWhere(db, fmt::format("`char_id` = '{}';", char_id));
|
||||||
if (buy_line_ids.empty()) {
|
if (buy_line_ids.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseBuyerBuyLinesRepository::DeleteWhere(
|
||||||
|
db, fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||||
|
);
|
||||||
|
BaseBuyerTradeItemsRepository::DeleteWhere(
|
||||||
|
db, fmt::format("`buyer_buy_lines_id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool DeleteBuyers(Database &db, uint32 char_zone_id, uint32 char_zone_instance_id)
|
||||||
|
{
|
||||||
|
auto buyers = GetWhere(
|
||||||
|
db,
|
||||||
|
fmt::format(
|
||||||
|
"`char_zone_id` = {} AND `char_zone_instance_id` = {}", char_zone_id, char_zone_instance_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (buyers.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> buyer_ids{};
|
||||||
|
std::vector<std::string> buy_line_ids{};
|
||||||
|
|
||||||
|
for (auto const &b: buyers) {
|
||||||
|
buyer_ids.push_back(std::to_string(b.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto buy_lines = BaseBuyerBuyLinesRepository::GetWhere(
|
||||||
|
db, fmt::format("`buyer_id` IN({})", Strings::Implode(", ", buyer_ids))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!buy_lines.empty()) {
|
||||||
|
for (auto const &bl: buy_lines) {
|
||||||
|
buy_line_ids.push_back(std::to_string(bl.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteWhere(db, fmt::format("`id` IN({});", Strings::Implode(", ", buyer_ids)));
|
||||||
|
if (buy_line_ids.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
BaseBuyerBuyLinesRepository::DeleteWhere(
|
BaseBuyerBuyLinesRepository::DeleteWhere(
|
||||||
db,
|
db,
|
||||||
fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids))
|
fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||||
);
|
);
|
||||||
BaseBuyerTradeItemsRepository::DeleteWhere(
|
BaseBuyerTradeItemsRepository::DeleteWhere(
|
||||||
db,
|
db,
|
||||||
fmt::format(
|
fmt::format("`buyer_buy_lines_id` IN({})", Strings::Implode(", ", buy_line_ids))
|
||||||
"`buyer_buy_lines_id` IN({})",
|
|
||||||
Strings::Implode(", ", buy_line_ids))
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,17 @@ 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 {}",
|
||||||
|
max_results)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
distinct_traders.reserve(results.RowCount());
|
distinct_traders.reserve(results.RowCount());
|
||||||
|
|
||||||
|
|||||||
+4
-6
@@ -156,6 +156,7 @@ RULE_REAL(Character, TradeskillUpPottery, 4.0, "Pottery skillup rate adjustment.
|
|||||||
RULE_REAL(Character, TradeskillUpResearch, 1.0, "Research skillup rate adjustment. Lower is faster")
|
RULE_REAL(Character, TradeskillUpResearch, 1.0, "Research skillup rate adjustment. Lower is faster")
|
||||||
RULE_REAL(Character, TradeskillUpTinkering, 2.0, "Tinkering skillup rate adjustment. Lower is faster")
|
RULE_REAL(Character, TradeskillUpTinkering, 2.0, "Tinkering skillup rate adjustment. Lower is faster")
|
||||||
RULE_REAL(Character, TradeskillUpTailoring, 2.0, "Tailoring skillup rate adjustment. Lower is faster")
|
RULE_REAL(Character, TradeskillUpTailoring, 2.0, "Tailoring skillup rate adjustment. Lower is faster")
|
||||||
|
RULE_REAL(Character, TradeskillUpMinChance, 2.5, "Determines the minimum percentage chance to gain a skill increase from a tradeskill. Cannot go below 2.5")
|
||||||
RULE_BOOL(Character, MarqueeHPUpdates, false, "Will show health percentage in center of screen if health lesser than 100%")
|
RULE_BOOL(Character, MarqueeHPUpdates, false, "Will show health percentage in center of screen if health lesser than 100%")
|
||||||
RULE_INT(Character, IksarCommonTongue, 95, "Starting value for Common Tongue for Iksars")
|
RULE_INT(Character, IksarCommonTongue, 95, "Starting value for Common Tongue for Iksars")
|
||||||
RULE_INT(Character, OgreCommonTongue, 95, "Starting value for Common Tongue for Ogres")
|
RULE_INT(Character, OgreCommonTongue, 95, "Starting value for Common Tongue for Ogres")
|
||||||
@@ -347,6 +348,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)
|
||||||
@@ -857,12 +859,6 @@ 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_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.")
|
||||||
@@ -883,6 +879,7 @@ RULE_INT(Bots, MinStatusToBypassSpawnLimit, 100, "Minimum status to bypass spawn
|
|||||||
RULE_INT(Bots, MinStatusBypassSpawnLimit, 120, "Spawn limit with status bypass. Default 120.")
|
RULE_INT(Bots, MinStatusBypassSpawnLimit, 120, "Spawn limit with status bypass. Default 120.")
|
||||||
RULE_INT(Bots, MinStatusToBypassCreateLimit, 100, "Minimum status to bypass create limit. Default 100.")
|
RULE_INT(Bots, MinStatusToBypassCreateLimit, 100, "Minimum status to bypass create limit. Default 100.")
|
||||||
RULE_INT(Bots, MinStatusBypassCreateLimit, 120, "Create limit with status bypass. Default 120.")
|
RULE_INT(Bots, MinStatusBypassCreateLimit, 120, "Create limit with status bypass. Default 120.")
|
||||||
|
RULE_INT(Bots, MinStatusToBypassBotLevelRequirement, 100, "Minimum status to bypass level requirement for bots. Default 100.")
|
||||||
RULE_BOOL(Bots, EnableBotTGB, true, "If enabled bots will cast group buffs as TGB.")
|
RULE_BOOL(Bots, EnableBotTGB, true, "If enabled bots will cast group buffs as TGB.")
|
||||||
RULE_BOOL(Bots, DoResponseAnimations, true, "If enabled bots will do animations to certain responses or commands.")
|
RULE_BOOL(Bots, DoResponseAnimations, true, "If enabled bots will do animations to certain responses or commands.")
|
||||||
RULE_INT(Bots, DefaultFollowDistance, 20, "Default 20. Distance a bot will follow behind.")
|
RULE_INT(Bots, DefaultFollowDistance, 20, "Default 20. Distance a bot will follow behind.")
|
||||||
@@ -928,6 +925,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)
|
||||||
|
|||||||
+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.4.0-dev" // always append -dev to the current version for custom-builds
|
#define CURRENT_VERSION "23.6.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 9321
|
#define CURRENT_BINARY_DATABASE_VERSION 9323
|
||||||
#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.4.0",
|
"version": "23.6.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/EQEmu/Server.git"
|
"url": "https://github.com/EQEmu/Server.git"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/golang/protobuf v1.3.2 // indirect
|
github.com/golang/protobuf v1.3.2 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
golang.org/x/crypto v0.35.0 // indirect
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
golang.org/x/net v0.36.0 // indirect
|
golang.org/x/net v0.38.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.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
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.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
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"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ void WorldserverCLI::EtlGetSettings(int argc, char **argv, argh::parser &cmd, st
|
|||||||
auto event_settings = player_event_logs.GetSettings();
|
auto event_settings = player_event_logs.GetSettings();
|
||||||
auto etl_details = player_event_logs.GetEtlSettings();
|
auto etl_details = player_event_logs.GetEtlSettings();
|
||||||
|
|
||||||
for (auto i = 0; i < PlayerEvent::EventType::MAX; i++) {
|
for (int i = PlayerEvent::GM_COMMAND; i < PlayerEvent::EventType::MAX; i++) {
|
||||||
player_events["event_id"] = event_settings[i].id;
|
player_events["event_id"] = event_settings[i].id;
|
||||||
player_events["enabled"] = event_settings[i].event_enabled ? true : false;
|
player_events["enabled"] = event_settings[i].event_enabled ? true : false;
|
||||||
player_events["retention"] = event_settings[i].retention_days;
|
player_events["retention"] = event_settings[i].retention_days;
|
||||||
|
|||||||
+99
-56
@@ -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);
|
||||||
|
|
||||||
@@ -1851,10 +1868,43 @@ std::map<uint32, ClientListEntry *> ClientList::GetGuildClientsWithTributeOptIn(
|
|||||||
return guild_members;
|
return guild_members;
|
||||||
}
|
}
|
||||||
|
|
||||||
#include <unordered_set>
|
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)
|
std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
|
||||||
{
|
{
|
||||||
|
if (RuleB(World, RealTimeCalculateGuilds)) {
|
||||||
std::vector<uint32_t> zone_server_ids;
|
std::vector<uint32_t> zone_server_ids;
|
||||||
std::unordered_set<uint32_t> seen_ids;
|
std::unordered_set<uint32_t> seen_ids;
|
||||||
|
|
||||||
@@ -1887,35 +1937,28 @@ std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
|
|||||||
return zone_server_ids;
|
return zone_server_ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint32_t> ClientList::GetZoneServersWithGMs()
|
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)
|
||||||
{
|
{
|
||||||
std::vector<uint32_t> zone_server_ids;
|
if (!cle || cle->Online() != CLE_Status::InZone || !cle->Server()) {
|
||||||
std::unordered_set<uint32_t> seen_ids;
|
return;
|
||||||
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()) {
|
uint32_t server_id = cle->Server()->GetID();
|
||||||
iterator.Advance();
|
|
||||||
continue;
|
// Add GM zone server if applicable
|
||||||
|
if (cle->GetGM()) {
|
||||||
|
m_gm_zone_server_ids.insert(server_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cle->Admin() > 0) {
|
// Add guild zone server if applicable
|
||||||
uint32_t id = cle->Server()->GetID();
|
if (cle->GuildID() > 0) {
|
||||||
if (seen_ids.insert(id).second) {
|
m_guild_zone_server_ids[cle->GuildID()].insert(server_id);
|
||||||
zone_server_ids.emplace_back(id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator.Advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
return zone_server_ids;
|
|
||||||
}
|
|
||||||
|
|||||||
+15
-3
@@ -60,15 +60,13 @@ public:
|
|||||||
void CLCheckStale();
|
void CLCheckStale();
|
||||||
void CLEKeepAlive(uint32 numupdates, uint32* wid);
|
void CLEKeepAlive(uint32 numupdates, uint32* wid);
|
||||||
void CLEAdd(uint32 login_server_id, const char* login_server_name, const char* login_name, const char* login_key, int16 world_admin = AccountStatus::Player, uint32 ip_address = 0, uint8 is_local=0);
|
void CLEAdd(uint32 login_server_id, const char* login_server_name, const char* login_name, const char* login_key, int16 world_admin = AccountStatus::Player, uint32 ip_address = 0, uint8 is_local=0);
|
||||||
std::vector<uint32_t> GetGuildZoneServers(uint32 guild_id);
|
|
||||||
std::vector<uint32_t> GetZoneServersWithGMs();
|
|
||||||
void UpdateClientGuild(uint32 char_id, uint32 guild_id);
|
void UpdateClientGuild(uint32 char_id, uint32 guild_id);
|
||||||
bool IsAccountInGame(uint32 iLSID);
|
bool IsAccountInGame(uint32 iLSID);
|
||||||
|
|
||||||
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);
|
||||||
@@ -78,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++; }
|
||||||
@@ -92,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_*/
|
||||||
|
|||||||
@@ -111,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)
|
||||||
@@ -127,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{};
|
||||||
@@ -174,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);
|
||||||
@@ -185,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;
|
||||||
}
|
}
|
||||||
@@ -192,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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//broadcast this packet to all zones.
|
//broadcast this packet to all zones.
|
||||||
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
|
zoneserver_list.SendPacketToBootedZones(pack);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include "dynamic_zone_manager.h"
|
#include "dynamic_zone_manager.h"
|
||||||
#include "ucs.h"
|
#include "ucs.h"
|
||||||
#include "clientlist.h"
|
#include "clientlist.h"
|
||||||
|
#include "../common/repositories/trader_repository.h"
|
||||||
|
#include "../common/repositories/buyer_repository.h"
|
||||||
|
|
||||||
extern uint32 numzones;
|
extern uint32 numzones;
|
||||||
extern EQ::Random emu_random;
|
extern EQ::Random emu_random;
|
||||||
@@ -84,6 +86,8 @@ void ZSList::Remove(const std::string &uuid)
|
|||||||
while (iter != zone_server_list.end()) {
|
while (iter != zone_server_list.end()) {
|
||||||
if ((*iter)->GetUUID().compare(uuid) == 0) {
|
if ((*iter)->GetUUID().compare(uuid) == 0) {
|
||||||
auto port = (*iter)->GetCPort();
|
auto port = (*iter)->GetCPort();
|
||||||
|
(*iter)->CheckToClearTraderAndBuyerTables();
|
||||||
|
|
||||||
zone_server_list.erase(iter);
|
zone_server_list.erase(iter);
|
||||||
|
|
||||||
if (port != 0) {
|
if (port != 0) {
|
||||||
|
|||||||
@@ -72,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:
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|||||||
#include "../common/repositories/guild_tributes_repository.h"
|
#include "../common/repositories/guild_tributes_repository.h"
|
||||||
#include "../common/skill_caps.h"
|
#include "../common/skill_caps.h"
|
||||||
#include "../common/server_reload_types.h"
|
#include "../common/server_reload_types.h"
|
||||||
|
#include "../common/repositories/trader_repository.h"
|
||||||
|
#include "../common/repositories/buyer_repository.h"
|
||||||
|
|
||||||
extern ClientList client_list;
|
extern ClientList client_list;
|
||||||
extern GroupLFPList LFPGroupList;
|
extern GroupLFPList LFPGroupList;
|
||||||
@@ -1860,3 +1862,19 @@ void ZoneServer::IncomingClient(Client* client) {
|
|||||||
SendPacket(pack);
|
SendPacket(pack);
|
||||||
delete pack;
|
delete pack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ZoneServer::CheckToClearTraderAndBuyerTables()
|
||||||
|
{
|
||||||
|
if (GetZoneID() == Zones::BAZAAR) {
|
||||||
|
TraderRepository::DeleteWhere(
|
||||||
|
database,
|
||||||
|
fmt::format("`char_zone_id` = {} AND `char_zone_instance_id` = {}", GetZoneID(), GetInstanceID()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
BuyerRepository::DeleteBuyers(database, GetZoneID(), GetInstanceID());
|
||||||
|
|
||||||
|
LogTradingDetail(
|
||||||
|
"Removed trader and buyer entries for Zone ID [{}] and Instance ID [{}]", GetZoneID(), GetInstanceID()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ 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 GetCurrentVersion() const { return CURRENT_VERSION; }
|
||||||
|
void CheckToClearTraderAndBuyerTables();
|
||||||
inline std::string GetCompileDate() const { return COMPILE_DATE; }
|
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); }
|
||||||
|
|||||||
+41
-26
@@ -3068,11 +3068,11 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
|||||||
else
|
else
|
||||||
SetAssistAggro(true);
|
SetAssistAggro(true);
|
||||||
|
|
||||||
bool wasengaged = IsEngaged();
|
bool was_engaged = IsEngaged();
|
||||||
Mob* owner = other->GetOwner();
|
Mob* owner = other->GetOwner();
|
||||||
Mob* mypet = GetPet();
|
Mob* my_pet = GetPet();
|
||||||
Mob* myowner = GetOwner();
|
Mob* my_owner = GetOwner();
|
||||||
Mob* targetmob = GetTarget();
|
Mob* target_mob = GetTarget();
|
||||||
bool on_hatelist = CheckAggro(other);
|
bool on_hatelist = CheckAggro(other);
|
||||||
|
|
||||||
AddRampage(other);
|
AddRampage(other);
|
||||||
@@ -3101,7 +3101,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
|||||||
if (IsPet()) {
|
if (IsPet()) {
|
||||||
if ((IsGHeld() || (IsHeld() && IsFocused())) && !on_hatelist) // we want them to be able to climb the hate list
|
if ((IsGHeld() || (IsHeld() && IsFocused())) && !on_hatelist) // we want them to be able to climb the hate list
|
||||||
return;
|
return;
|
||||||
if ((IsHeld() || IsPetStop() || IsPetRegroup()) && !wasengaged) // not 100% sure on stop/regroup kind of hard to test, but regroup is like "classic hold"
|
if ((IsHeld() || IsPetStop() || IsPetRegroup()) && !was_engaged) // not 100% sure on stop/regroup kind of hard to test, but regroup is like "classic hold"
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3134,7 +3134,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (other == myowner) {
|
if (other == my_owner) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3236,26 +3236,39 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mypet && !mypet->IsHeld() && !mypet->IsPetStop()) { // I have a pet, add other to it
|
if (my_pet) {
|
||||||
if (
|
bool aggro_immunity = my_pet->GetSpecialAbility(SpecialAbility::AggroImmunity);
|
||||||
!mypet->IsFamiliar() &&
|
bool bot_aggro_immunity = IsBot() && my_pet->GetSpecialAbility(SpecialAbility::BotAggroImmunity);
|
||||||
!mypet->GetSpecialAbility(SpecialAbility::AggroImmunity) &&
|
bool client_aggro_immunity = IsClient() && my_pet->GetSpecialAbility(SpecialAbility::ClientAggroImmunity);
|
||||||
!(IsBot() && mypet->GetSpecialAbility(SpecialAbility::BotAggroImmunity)) &&
|
bool npc_aggro_immunity = IsNPC() && my_pet->GetSpecialAbility(SpecialAbility::NPCAggroImmunity);
|
||||||
!(IsClient() && mypet->GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) &&
|
bool can_add_to_hatelist = !my_pet->IsFamiliar() &&
|
||||||
!(IsNPC() && mypet->GetSpecialAbility(SpecialAbility::NPCAggroImmunity))
|
!aggro_immunity &&
|
||||||
) {
|
!bot_aggro_immunity &&
|
||||||
mypet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
|
!client_aggro_immunity &&
|
||||||
|
!npc_aggro_immunity;
|
||||||
|
|
||||||
|
if (can_add_to_hatelist) {
|
||||||
|
bool bot_with_controllable_pet = IsBot() && CastToBot()->HasControllablePet(BotAnimEmpathy::Attack);
|
||||||
|
|
||||||
|
if (!IsBot() || bot_with_controllable_pet) {
|
||||||
|
my_pet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (myowner) { // I am a pet, add other to owner if it's NPC/LD
|
}
|
||||||
if (
|
else if (my_owner) { // I am a pet, add other to owner if it's NPC/LD
|
||||||
myowner->IsAIControlled() &&
|
if (my_owner->IsAIControlled()) {
|
||||||
!myowner->GetSpecialAbility(SpecialAbility::AggroImmunity) &&
|
bool aggro_immunity = my_owner->GetSpecialAbility(SpecialAbility::AggroImmunity);
|
||||||
!(myowner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity)) &&
|
bool bot_aggro_immunity = my_owner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity);
|
||||||
!(myowner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) &&
|
bool client_aggro_immunity = my_owner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity);
|
||||||
!(myowner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity))
|
bool npc_aggro_immunity = my_owner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity);
|
||||||
) {
|
bool can_add_to_hatelist = !aggro_immunity &&
|
||||||
myowner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
|
!bot_aggro_immunity &&
|
||||||
|
!client_aggro_immunity &&
|
||||||
|
!npc_aggro_immunity;
|
||||||
|
|
||||||
|
if (can_add_to_hatelist) {
|
||||||
|
my_owner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3264,7 +3277,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
|
|||||||
entity_list.AddTempPetsToHateList(this, other, bFrenzy);
|
entity_list.AddTempPetsToHateList(this, other, bFrenzy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wasengaged) {
|
if (!was_engaged) {
|
||||||
if (IsNPC() && other->IsClient() && other->CastToClient()) {
|
if (IsNPC() && other->IsClient() && other->CastToClient()) {
|
||||||
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_AGGRO)) {
|
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_AGGRO)) {
|
||||||
parse->EventNPC(EVENT_AGGRO, CastToNPC(), other, "", 0);
|
parse->EventNPC(EVENT_AGGRO, CastToNPC(), other, "", 0);
|
||||||
@@ -6677,7 +6690,9 @@ void Client::SetAttackTimer()
|
|||||||
else
|
else
|
||||||
speed = static_cast<int>(speed + ((hhe / 100.0f) * delay));
|
speed = static_cast<int>(speed + ((hhe / 100.0f) * delay));
|
||||||
}
|
}
|
||||||
TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true, true);
|
|
||||||
|
bool reinit = !TimerToUse->Enabled();
|
||||||
|
TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), reinit, reinit);
|
||||||
|
|
||||||
if (i == EQ::invslot::slotPrimary) {
|
if (i == EQ::invslot::slotPrimary) {
|
||||||
primary_speed = speed;
|
primary_speed = speed;
|
||||||
|
|||||||
+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;
|
||||||
}
|
}
|
||||||
|
|||||||
+312
-252
@@ -245,6 +245,8 @@ Bot::Bot(
|
|||||||
|
|
||||||
EquipBot();
|
EquipBot();
|
||||||
|
|
||||||
|
m_combat_jitter_timer.Start();
|
||||||
|
|
||||||
if (GetClass() == Class::Rogue) {
|
if (GetClass() == Class::Rogue) {
|
||||||
m_rogue_evade_timer.Start();
|
m_rogue_evade_timer.Start();
|
||||||
}
|
}
|
||||||
@@ -2100,10 +2102,6 @@ void Bot::SetGuardMode() {
|
|||||||
StopMoving();
|
StopMoving();
|
||||||
m_GuardPoint = GetPosition();
|
m_GuardPoint = GetPosition();
|
||||||
SetGuardFlag();
|
SetGuardFlag();
|
||||||
|
|
||||||
if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
|
|
||||||
GetPet()->StopMoving();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::SetHoldMode() {
|
void Bot::SetHoldMode() {
|
||||||
@@ -2186,8 +2184,7 @@ void Bot::AI_Process()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (HOLDING || (raid && r_group == RAID_GROUPLESS)) {
|
if (HOLDING || (raid && r_group == RAID_GROUPLESS)) {
|
||||||
glm::vec3 Goal(0, 0, 0);
|
TryNonCombatMovementChecks(bot_owner, follow_mob);
|
||||||
TryNonCombatMovementChecks(bot_owner, follow_mob, Goal);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2217,8 +2214,6 @@ void Bot::AI_Process()
|
|||||||
}
|
}
|
||||||
|
|
||||||
//ALT COMBAT (ACQUIRE HATE)
|
//ALT COMBAT (ACQUIRE HATE)
|
||||||
glm::vec3 Goal(0, 0, 0);
|
|
||||||
|
|
||||||
// We have aggro to choose from
|
// We have aggro to choose from
|
||||||
if (IsEngaged()) {
|
if (IsEngaged()) {
|
||||||
if (rest_timer.Enabled()) {
|
if (rest_timer.Enabled()) {
|
||||||
@@ -2271,7 +2266,7 @@ void Bot::AI_Process()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This causes conflicts with default pet handler (bounces between targets)
|
// This causes conflicts with default pet handler (bounces between targets)
|
||||||
if (NOT_PULLING_BOT && NOT_RETURNING_BOT && HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
|
if (NOT_PULLING_BOT && NOT_RETURNING_BOT && HasControllablePet(BotAnimEmpathy::Attack)) {
|
||||||
// We don't add to hate list here because it's assumed to already be on the list
|
// We don't add to hate list here because it's assumed to already be on the list
|
||||||
GetPet()->SetTarget(tar);
|
GetPet()->SetTarget(tar);
|
||||||
}
|
}
|
||||||
@@ -2288,12 +2283,12 @@ void Bot::AI_Process()
|
|||||||
bool front_mob = InFrontMob(tar, GetX(), GetY());
|
bool front_mob = InFrontMob(tar, GetX(), GetY());
|
||||||
bool behind_mob = BehindMob(tar, GetX(), GetY());
|
bool behind_mob = BehindMob(tar, GetX(), GetY());
|
||||||
bool stop_melee_level = GetLevel() >= GetStopMeleeLevel();
|
bool stop_melee_level = GetLevel() >= GetStopMeleeLevel();
|
||||||
|
|
||||||
tar_distance = sqrt(tar_distance); // sqrt this for future calculations
|
tar_distance = sqrt(tar_distance); // sqrt this for future calculations
|
||||||
// Item variables
|
|
||||||
const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary);
|
const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary);
|
||||||
const EQ::ItemInstance* s_item = GetBotItem(EQ::invslot::slotSecondary);
|
const EQ::ItemInstance* s_item = GetBotItem(EQ::invslot::slotSecondary);
|
||||||
|
|
||||||
CombatRangeInput input = {
|
CombatRangeInput i = {
|
||||||
.target = tar,
|
.target = tar,
|
||||||
.target_distance = tar_distance,
|
.target_distance = tar_distance,
|
||||||
.stop_melee_level = stop_melee_level,
|
.stop_melee_level = stop_melee_level,
|
||||||
@@ -2301,7 +2296,7 @@ void Bot::AI_Process()
|
|||||||
.s_item = s_item
|
.s_item = s_item
|
||||||
};
|
};
|
||||||
|
|
||||||
CombatRangeOutput o = EvaluateCombatRange(input);
|
CombatRangeOutput o = EvaluateCombatRange(i);
|
||||||
|
|
||||||
// Combat range variables
|
// Combat range variables
|
||||||
bool at_combat_range = o.at_combat_range;
|
bool at_combat_range = o.at_combat_range;
|
||||||
@@ -2311,20 +2306,36 @@ void Bot::AI_Process()
|
|||||||
|
|
||||||
// PULLING FLAG (ACTIONABLE RANGE)
|
// PULLING FLAG (ACTIONABLE RANGE)
|
||||||
|
|
||||||
if (PULLING_BOT || RETURNING_BOT) {
|
if (PULLING_BOT) {
|
||||||
if (!TargetValidation(tar)) { return; }
|
if (!TargetValidation(tar)) {
|
||||||
|
SetPullFlag(false);
|
||||||
|
SetPullingFlag(false);
|
||||||
|
bot_owner->SetBotPulling(false);
|
||||||
|
|
||||||
|
if (GetPet()) {
|
||||||
|
GetPet()->SetPetOrder(SPO_Follow);
|
||||||
|
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (RuleB(Bots, BotsRequireLoS) && !HasLoS()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (at_combat_range) {
|
if (!at_combat_range && RuleB(Bots, UseSpellPulling)) {
|
||||||
if (
|
uint16 pull_spell_id = RuleI(Bots, PullSpellID);
|
||||||
!tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
|
|
||||||
|
if (IsValidSpell(pull_spell_id) && tar_distance <= spells[pull_spell_id].range) {
|
||||||
|
at_combat_range = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (at_combat_range && DoLosChecks(tar)) {
|
||||||
|
bool ai_cast_successful = false;
|
||||||
|
bool can_range_attack = !tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
|
||||||
RuleB(Bots, AllowRangedPulling) &&
|
RuleB(Bots, AllowRangedPulling) &&
|
||||||
IsBotRanged() &&
|
IsBotRanged() &&
|
||||||
ranged_timer.Check(false)
|
ranged_timer.Check(false);
|
||||||
) {
|
|
||||||
|
if (can_range_attack) {
|
||||||
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
||||||
|
|
||||||
if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) {
|
if (BotRangedAttack(tar) && CheckDoubleRangedAttack()) {
|
||||||
@@ -2336,33 +2347,36 @@ void Bot::AI_Process()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
bool can_ai_spell_pull = RuleB(Bots, AllowAISpellPulling) &&
|
||||||
RuleB(Bots, AllowAISpellPulling) &&
|
|
||||||
!IsBotNonSpellFighter() &&
|
!IsBotNonSpellFighter() &&
|
||||||
AI_HasSpells()
|
AI_HasSpells();
|
||||||
) {
|
|
||||||
|
if (can_ai_spell_pull) {
|
||||||
|
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
||||||
SetPullingSpell(true);
|
SetPullingSpell(true);
|
||||||
AI_EngagedCastCheck();
|
ai_cast_successful = AI_EngagedCastCheck();
|
||||||
SetPullingSpell(false);
|
SetPullingSpell(false);
|
||||||
|
|
||||||
|
if (ai_cast_successful) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RuleB(Bots, UseSpellPulling)) {
|
if (RuleB(Bots, UseSpellPulling)) {
|
||||||
uint16 spell_id = RuleI(Bots, PullSpellID);
|
uint16 pull_spell_id = RuleI(Bots, PullSpellID);
|
||||||
|
|
||||||
if (tar_distance <= spells[spell_id].range) {
|
if (IsValidSpell(pull_spell_id) && tar_distance <= spells[pull_spell_id].range) {
|
||||||
StopMoving();
|
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
||||||
SetPullingSpell(true);
|
SetPullingSpell(true);
|
||||||
CastSpell(spell_id, tar->GetID());
|
CastSpell(pull_spell_id, tar->GetID());
|
||||||
SetPullingSpell(false);
|
SetPullingSpell(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
TryPursueTarget(leash_distance, Goal);
|
TryPursueTarget(leash_distance);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2385,52 +2399,30 @@ void Bot::AI_Process()
|
|||||||
(bot_owner->GetBotPulling() && NOT_RETURNING_BOT);
|
(bot_owner->GetBotPulling() && NOT_RETURNING_BOT);
|
||||||
|
|
||||||
if (!other_bot_pulling && at_combat_range) {
|
if (!other_bot_pulling && at_combat_range) {
|
||||||
bool jitter_cooldown = false;
|
CombatPositioningInput cpi {
|
||||||
|
.tar = tar,
|
||||||
if (m_combat_jitter_timer.GetRemainingTime() > 1 && m_combat_jitter_timer.Enabled()) {
|
.stop_melee_level = stop_melee_level,
|
||||||
jitter_cooldown = true;
|
.tar_distance = tar_distance,
|
||||||
}
|
.melee_distance_min = melee_distance_min,
|
||||||
|
.melee_distance = melee_distance,
|
||||||
if (
|
.melee_distance_max = melee_distance_max,
|
||||||
IsMoving() ||
|
.behind_mob = behind_mob,
|
||||||
GetCombatJitterFlag() ||
|
.front_mob = front_mob
|
||||||
GetCombatOutOfRangeJitterFlag()
|
};
|
||||||
) {
|
|
||||||
if (
|
|
||||||
!GetCombatJitterFlag() ||
|
|
||||||
!IsMoving() ||
|
|
||||||
GetCombatOutOfRangeJitterFlag()
|
|
||||||
) {
|
|
||||||
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (DoCombatPositioning(cpi) && IsMoving()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
!jitter_cooldown &&
|
|
||||||
AI_movement_timer->Check() &&
|
|
||||||
(!spellend_timer.Enabled() || GetClass() == Class::Bard)
|
|
||||||
) {
|
|
||||||
DoCombatPositioning(tar, Goal, stop_melee_level, tar_distance, melee_distance_min, melee_distance, melee_distance_max, behind_mob, front_mob);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!IsSitting() && !IsFacingMob(tar)) {
|
if (!IsSitting() && !IsFacingMob(tar)) {
|
||||||
FaceTarget(tar);
|
FaceTarget(tar);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsBotNonSpellFighter() && AI_HasSpells() && AI_EngagedCastCheck()) {
|
if (!IsBotNonSpellFighter() && AI_HasSpells() && AI_EngagedCastCheck()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsMoving()) {
|
|
||||||
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
|
!tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
|
||||||
IsBotRanged() &&
|
IsBotRanged() &&
|
||||||
@@ -2479,7 +2471,7 @@ void Bot::AI_Process()
|
|||||||
|
|
||||||
// ENGAGED NOT AT COMBAT RANGE
|
// ENGAGED NOT AT COMBAT RANGE
|
||||||
|
|
||||||
else if (!other_bot_pulling && !TryPursueTarget(leash_distance, Goal)) {
|
else if (!other_bot_pulling && !TryPursueTarget(leash_distance)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2492,7 +2484,7 @@ void Bot::AI_Process()
|
|||||||
TryMeditate();
|
TryMeditate();
|
||||||
}
|
}
|
||||||
else { // Out-of-combat behavior
|
else { // Out-of-combat behavior
|
||||||
DoOutOfCombatChecks(bot_owner, follow_mob, Goal, leash_distance, fm_distance);
|
DoOutOfCombatChecks(bot_owner, follow_mob, leash_distance, fm_distance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2509,8 +2501,10 @@ bool Bot::TryBardMovementCasts() {// Basically, bard bots get a chance to cast i
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal) {// Non-engaged movement checks
|
bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob) {// Non-engaged movement checks
|
||||||
if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == Class::Bard)) {
|
if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == Class::Bard)) {
|
||||||
|
glm::vec3 Goal(0, 0, 0);
|
||||||
|
|
||||||
if (GUARDING) {
|
if (GUARDING) {
|
||||||
Goal = GetGuardPoint();
|
Goal = GetGuardPoint();
|
||||||
}
|
}
|
||||||
@@ -2564,7 +2558,7 @@ bool Bot::TryIdleChecks(float fm_distance) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance) {
|
void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance) {
|
||||||
SetAttackFlag(false);
|
SetAttackFlag(false);
|
||||||
SetCombatRoundForAlerts(false);
|
SetCombatRoundForAlerts(false);
|
||||||
SetAttackingFlag(false);
|
SetAttackingFlag(false);
|
||||||
@@ -2572,6 +2566,12 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goa
|
|||||||
if (PULLING_BOT || RETURNING_BOT || !bot_owner->GetBotPulling()) {
|
if (PULLING_BOT || RETURNING_BOT || !bot_owner->GetBotPulling()) {
|
||||||
SetPullingFlag(false);
|
SetPullingFlag(false);
|
||||||
SetReturningFlag(false);
|
SetReturningFlag(false);
|
||||||
|
bot_owner->SetBotPulling(false);
|
||||||
|
|
||||||
|
if (GetPet()) {
|
||||||
|
GetPet()->SetPetOrder(SPO_Follow);
|
||||||
|
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryAutoDefend(bot_owner, leash_distance) ) {
|
if (TryAutoDefend(bot_owner, leash_distance) ) {
|
||||||
@@ -2580,14 +2580,7 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goa
|
|||||||
|
|
||||||
SetTarget(nullptr);
|
SetTarget(nullptr);
|
||||||
|
|
||||||
if (
|
if (HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||||
HasPet() &&
|
|
||||||
(
|
|
||||||
GetClass() != Class::Enchanter ||
|
|
||||||
GetPet()->GetPetType() != petAnimation ||
|
|
||||||
GetAA(aaAnimationEmpathy) >= 1
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
GetPet()->WipeHateList();
|
GetPet()->WipeHateList();
|
||||||
GetPet()->SetTarget(nullptr);
|
GetPet()->SetTarget(nullptr);
|
||||||
}
|
}
|
||||||
@@ -2597,7 +2590,7 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ok to idle
|
// Ok to idle
|
||||||
if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) {
|
if (TryNonCombatMovementChecks(bot_owner, follow_mob)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2750,7 +2743,7 @@ bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) {
|
|||||||
SetTarget(hater);
|
SetTarget(hater);
|
||||||
SetAttackingFlag();
|
SetAttackingFlag();
|
||||||
|
|
||||||
if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
|
if (HasControllablePet(BotAnimEmpathy::Attack)) {
|
||||||
GetPet()->AddToHateList(hater, 1);
|
GetPet()->AddToHateList(hater, 1);
|
||||||
GetPet()->SetTarget(hater);
|
GetPet()->SetTarget(hater);
|
||||||
}
|
}
|
||||||
@@ -2808,27 +2801,19 @@ bool Bot::TryMeditate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This code actually gets processed when we are too far away from target and have not engaged yet
|
// This code actually gets processed when we are too far away from target and have not engaged yet
|
||||||
bool Bot::TryPursueTarget(float leash_distance, glm::vec3& Goal) {
|
bool Bot::TryPursueTarget(float leash_distance) {
|
||||||
if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) {
|
if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) {
|
||||||
if (GetTarget() && !IsRooted()) {
|
if (GetTarget() && !IsRooted()) {
|
||||||
LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName());
|
LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName());
|
||||||
Goal = GetTarget()->GetPosition();
|
glm::vec3 Goal = GetTarget()->GetPosition();
|
||||||
|
|
||||||
if (DistanceSquared(m_Position, Goal) <= leash_distance) {
|
if (DistanceSquared(m_Position, Goal) <= leash_distance) {
|
||||||
RunTo(Goal.x, Goal.y, Goal.z);
|
RunTo(Goal.x, Goal.y, Goal.z);
|
||||||
SetCombatOutOfRangeJitter();
|
|
||||||
} else {
|
} else {
|
||||||
WipeHateList();
|
WipeHateList();
|
||||||
SetTarget(nullptr);
|
SetTarget(nullptr);
|
||||||
|
|
||||||
if (
|
if (HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||||
HasPet() &&
|
|
||||||
(
|
|
||||||
GetClass() != Class::Enchanter ||
|
|
||||||
GetPet()->GetPetType() != petAnimation ||
|
|
||||||
GetAA(aaAnimationEmpathy) >= 2
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
GetPet()->WipeHateList();
|
GetPet()->WipeHateList();
|
||||||
GetPet()->SetTarget(nullptr);
|
GetPet()->SetTarget(nullptr);
|
||||||
}
|
}
|
||||||
@@ -3130,8 +3115,8 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
|
|||||||
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage();
|
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage();
|
||||||
|
|
||||||
if (IsTaunting()) { // Taunting bots
|
if (IsTaunting()) { // Taunting bots
|
||||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerTauntingMeleeDistanceMultiplier);
|
o.melee_distance_min = o.melee_distance_max * 0.25f;
|
||||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperTauntingMeleeDistanceMultiplier);
|
o.melee_distance = o.melee_distance_max * 0.45f;
|
||||||
}
|
}
|
||||||
else if (IsBotRanged()) { // Archers/Throwers
|
else if (IsBotRanged()) { // Archers/Throwers
|
||||||
float min_distance = RuleI(Combat, MinRangedAttackDist);
|
float min_distance = RuleI(Combat, MinRangedAttackDist);
|
||||||
@@ -3139,22 +3124,22 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
|
|||||||
float desired_range = GetBotDistanceRanged();
|
float desired_range = GetBotDistanceRanged();
|
||||||
|
|
||||||
max_distance = (max_distance == 0 ? desired_range : max_distance); // stay ranged even if items/ammo aren't correct
|
max_distance = (max_distance == 0 ? desired_range : max_distance); // stay ranged even if items/ammo aren't correct
|
||||||
o.melee_distance_min = std::max(min_distance, (desired_range / 2));
|
o.melee_distance_min = std::max(min_distance, (desired_range * 0.75f));
|
||||||
o.melee_distance = std::min(max_distance, desired_range);
|
o.melee_distance = std::min(max_distance, desired_range);
|
||||||
}
|
}
|
||||||
else if (input.stop_melee_level) { // Casters
|
else if (input.stop_melee_level) { // Casters
|
||||||
float desired_range = GetBotDistanceRanged();
|
float desired_range = GetBotDistanceRanged();
|
||||||
|
|
||||||
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range / 2));
|
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range * 0.75f));
|
||||||
o.melee_distance = std::max((o.melee_distance_max * 1.25f), desired_range);
|
o.melee_distance = std::max((o.melee_distance_max * 1.25f), desired_range);
|
||||||
}
|
}
|
||||||
else if (GetMaxMeleeRange()) { // Melee bots set to max melee range
|
else if (GetMaxMeleeRange()) { // Melee bots set to max melee range
|
||||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMaxMeleeRangeDistanceMultiplier);
|
o.melee_distance_min = o.melee_distance_max * 0.80f;
|
||||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMaxMeleeRangeDistanceMultiplier);
|
o.melee_distance = o.melee_distance_max * 0.95f;
|
||||||
}
|
}
|
||||||
else { // Regular melee
|
else { // Regular melee
|
||||||
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMeleeDistanceMultiplier);
|
o.melee_distance_min = o.melee_distance_max * 0.30f;
|
||||||
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMeleeDistanceMultiplier);
|
o.melee_distance = o.melee_distance_max * 0.65f;
|
||||||
}
|
}
|
||||||
|
|
||||||
o.at_combat_range = (input.target_distance <= o.melee_distance);
|
o.at_combat_range = (input.target_distance <= o.melee_distance);
|
||||||
@@ -3213,7 +3198,8 @@ bool Bot::IsValidTarget(
|
|||||||
bot_owner->SetBotPulling(false);
|
bot_owner->SetBotPulling(false);
|
||||||
|
|
||||||
if (GetPet()) {
|
if (GetPet()) {
|
||||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
GetPet()->SetPetOrder(SPO_Follow);
|
||||||
|
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3247,7 +3233,8 @@ Mob* Bot::GetBotTarget(Client* bot_owner)
|
|||||||
bot_owner->SetBotPulling(false);
|
bot_owner->SetBotPulling(false);
|
||||||
|
|
||||||
if (GetPet()) {
|
if (GetPet()) {
|
||||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
GetPet()->SetPetOrder(SPO_Follow);
|
||||||
|
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3270,15 +3257,11 @@ bool Bot::TargetValidation(Mob* other) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_distance) {
|
bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_distance) {
|
||||||
auto engage_range = (GetBotDistanceRanged() < 30 ? 30 : GetBotDistanceRanged());
|
bool target_check = !GetTarget() || Distance(GetPosition(), GetTarget()->GetPosition()) <= 75.0f;
|
||||||
|
bool returned_check = (NOT_GUARDING && fm_distance <= GetFollowDistance()) ||
|
||||||
|
(GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) <= GetFollowDistance());
|
||||||
|
|
||||||
if (
|
if (target_check && returned_check) { // Once we're back, clear blocking flags so everyone else can join in
|
||||||
(GetTarget() && Distance(GetPosition(), GetTarget()->GetPosition()) <= engage_range) &&
|
|
||||||
(
|
|
||||||
(NOT_GUARDING && fm_distance <= GetFollowDistance()) ||
|
|
||||||
(GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) <= GetFollowDistance())
|
|
||||||
)
|
|
||||||
) { // Once we're back, clear blocking flags so everyone else can join in
|
|
||||||
WipeHateList();
|
WipeHateList();
|
||||||
SetTarget(nullptr);
|
SetTarget(nullptr);
|
||||||
SetPullingFlag(false);
|
SetPullingFlag(false);
|
||||||
@@ -3286,9 +3269,10 @@ bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_dist
|
|||||||
bot_owner->SetBotPulling(false);
|
bot_owner->SetBotPulling(false);
|
||||||
|
|
||||||
if (GetPet()) {
|
if (GetPet()) {
|
||||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
GetPet()->SetPetOrder(SPO_Follow);
|
||||||
|
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||||
|
|
||||||
if (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1) {
|
if (HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||||
GetPet()->WipeHateList();
|
GetPet()->WipeHateList();
|
||||||
GetPet()->SetTarget(nullptr);
|
GetPet()->SetTarget(nullptr);
|
||||||
}
|
}
|
||||||
@@ -3329,7 +3313,8 @@ bool Bot::PullingFlagChecks(Client* bot_owner) {
|
|||||||
bot_owner->SetBotPulling(false);
|
bot_owner->SetBotPulling(false);
|
||||||
|
|
||||||
if (GetPet()) {
|
if (GetPet()) {
|
||||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
GetPet()->SetPetOrder(SPO_Follow);
|
||||||
|
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -3338,11 +3323,16 @@ bool Bot::PullingFlagChecks(Client* bot_owner) {
|
|||||||
SetPullingFlag(false);
|
SetPullingFlag(false);
|
||||||
SetReturningFlag();
|
SetReturningFlag();
|
||||||
|
|
||||||
if (HasPet() &&
|
Mob* my_pet = GetPet();
|
||||||
(GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) {
|
|
||||||
|
|
||||||
GetPet()->WipeHateList();
|
if (my_pet) {
|
||||||
GetPet()->SetTarget(nullptr);
|
if (HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||||
|
my_pet->WipeHateList();
|
||||||
|
my_pet->SetTarget(nullptr);
|
||||||
|
} else {
|
||||||
|
my_pet->AddToHateList(GetTarget(), 1);
|
||||||
|
my_pet->SetTarget(GetTarget());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetPlayerState() & static_cast<uint32>(PlayerState::Aggressive)) {
|
if (GetPlayerState() & static_cast<uint32>(PlayerState::Aggressive)) {
|
||||||
@@ -3504,7 +3494,7 @@ Client* Bot::SetLeashOwner(Client* bot_owner, Group* bot_group, Raid* raid, uint
|
|||||||
|
|
||||||
void Bot::SetOwnerTarget(Client* bot_owner) {
|
void Bot::SetOwnerTarget(Client* bot_owner) {
|
||||||
if (GetPet() && (PULLING_BOT || RETURNING_BOT)) {
|
if (GetPet() && (PULLING_BOT || RETURNING_BOT)) {
|
||||||
GetPet()->SetPetOrder(m_previous_pet_order);
|
GetPet()->SetPetOrder(SPO_Follow);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetAttackFlag(false);
|
SetAttackFlag(false);
|
||||||
@@ -3524,7 +3514,7 @@ void Bot::SetOwnerTarget(Client* bot_owner) {
|
|||||||
SetTarget(attack_target);
|
SetTarget(attack_target);
|
||||||
SetAttackingFlag();
|
SetAttackingFlag();
|
||||||
|
|
||||||
if (GetPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
|
if (HasControllablePet(BotAnimEmpathy::Attack)) {
|
||||||
GetPet()->WipeHateList();
|
GetPet()->WipeHateList();
|
||||||
GetPet()->AddToHateList(attack_target, 1);
|
GetPet()->AddToHateList(attack_target, 1);
|
||||||
GetPet()->SetTarget(attack_target);
|
GetPet()->SetTarget(attack_target);
|
||||||
@@ -3542,20 +3532,21 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) {
|
|||||||
SetReturningFlag(false);
|
SetReturningFlag(false);
|
||||||
bot_owner->SetBotPulling(false);
|
bot_owner->SetBotPulling(false);
|
||||||
|
|
||||||
|
if (GetPet()) {
|
||||||
|
GetPet()->SetPetOrder(SPO_Follow);
|
||||||
|
GetPet()->CastToNPC()->SaveGuardSpot(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (NOT_HOLDING && NOT_PASSIVE) {
|
if (NOT_HOLDING && NOT_PASSIVE) {
|
||||||
auto pull_target = bot_owner->GetTarget();
|
auto pull_target = bot_owner->GetTarget();
|
||||||
|
|
||||||
if (pull_target) {
|
if (pull_target) {
|
||||||
if (raid) {
|
|
||||||
const auto msg = fmt::format("Pulling {}.", pull_target->GetCleanName());
|
|
||||||
raid->RaidSay(msg.c_str(), GetCleanName(), 0, 100);
|
|
||||||
} else {
|
|
||||||
RaidGroupSay(
|
RaidGroupSay(
|
||||||
fmt::format(
|
fmt::format(
|
||||||
"Pulling {}.",
|
"Pulling {}.",
|
||||||
pull_target->GetCleanName()
|
pull_target->GetCleanName()
|
||||||
).c_str()
|
).c_str()
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
InterruptSpell();
|
InterruptSpell();
|
||||||
WipeHateList();
|
WipeHateList();
|
||||||
@@ -3564,9 +3555,11 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) {
|
|||||||
SetPullingFlag();
|
SetPullingFlag();
|
||||||
bot_owner->SetBotPulling();
|
bot_owner->SetBotPulling();
|
||||||
|
|
||||||
if (HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1)) {
|
if (GetPet()) {
|
||||||
GetPet()->WipeHateList();
|
GetPet()->WipeHateList();
|
||||||
GetPet()->SetTarget(nullptr);
|
GetPet()->SetTarget(nullptr);
|
||||||
|
|
||||||
|
if (HasControllablePet(BotAnimEmpathy::Guard)) {
|
||||||
m_previous_pet_order = GetPet()->GetPetOrder();
|
m_previous_pet_order = GetPet()->GetPetOrder();
|
||||||
GetPet()->CastToNPC()->SaveGuardSpot(GetPosition());
|
GetPet()->CastToNPC()->SaveGuardSpot(GetPosition());
|
||||||
GetPet()->SetPetOrder(SPO_Guard);
|
GetPet()->SetPetOrder(SPO_Guard);
|
||||||
@@ -3574,6 +3567,7 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Bot::Depop() {
|
void Bot::Depop() {
|
||||||
WipeHateList();
|
WipeHateList();
|
||||||
@@ -8737,6 +8731,86 @@ bool Bot::CheckCampSpawnConditions(Client* c) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Bot::CheckHighEnoughLevelForBots(Client* c, uint8 bot_class) {
|
||||||
|
auto bot_character_level = c->GetBotRequiredLevel(bot_class);
|
||||||
|
bool not_high_enough_level = bot_character_level >= 0 && c->GetLevel() < bot_character_level;
|
||||||
|
|
||||||
|
if (not_high_enough_level) {
|
||||||
|
c->Message(
|
||||||
|
Chat::White,
|
||||||
|
fmt::format(
|
||||||
|
"You must be level {} to spawn {}bots.",
|
||||||
|
bot_character_level,
|
||||||
|
bot_class ? GetClassIDName(bot_class) : ""
|
||||||
|
).c_str()
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bot::CheckCreateLimit(Client* c, uint32 bot_count, uint8 bot_class) {
|
||||||
|
auto bot_creation_limit = c->GetBotCreationLimit(bot_class);
|
||||||
|
bool is_beyond_spawn_limit = bot_creation_limit >= 0 && bot_count >= bot_creation_limit;
|
||||||
|
|
||||||
|
if (is_beyond_spawn_limit) {
|
||||||
|
std::string message;
|
||||||
|
|
||||||
|
if (bot_creation_limit) {
|
||||||
|
message = fmt::format(
|
||||||
|
"You cannot create anymore than {} {}bot{}.",
|
||||||
|
bot_creation_limit,
|
||||||
|
bot_class ? GetClassIDName(bot_class) : "",
|
||||||
|
bot_creation_limit != 1 ? "s" : ""
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
message = fmt::format(
|
||||||
|
"You cannot create any {}bots.",
|
||||||
|
bot_class ? GetClassIDName(bot_class) : ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
c->Message(Chat::Yellow, message.c_str());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bot::CheckSpawnLimit(Client* c, uint8 bot_class) {
|
||||||
|
auto bot_spawn_limit = c->GetBotSpawnLimit(bot_class);
|
||||||
|
auto spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID(), bot_class);
|
||||||
|
bool is_beyond_spawn_limit = bot_spawn_limit >= 0 && spawned_bot_count >= bot_spawn_limit;
|
||||||
|
|
||||||
|
if (is_beyond_spawn_limit) {
|
||||||
|
std::string message;
|
||||||
|
|
||||||
|
if (bot_spawn_limit) {
|
||||||
|
message = fmt::format(
|
||||||
|
"You cannot have more than {} spawned {}bot{}.",
|
||||||
|
bot_spawn_limit,
|
||||||
|
bot_class ? GetClassIDName(bot_class) : "",
|
||||||
|
bot_spawn_limit != 1 ? "s" : ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
message = fmt::format(
|
||||||
|
"You are not currently allowed to spawn any {}bots.",
|
||||||
|
bot_class ? GetClassIDName(bot_class) : ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
c->Message(Chat::White, message.c_str());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Bot::AddBotStartingItems(uint16 race_id, uint8 class_id)
|
void Bot::AddBotStartingItems(uint16 race_id, uint8 class_id)
|
||||||
{
|
{
|
||||||
if (!IsPlayerRace(race_id) || !IsPlayerClass(class_id)) {
|
if (!IsPlayerRace(race_id) || !IsPlayerClass(class_id)) {
|
||||||
@@ -9738,7 +9812,7 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool precheck
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
&&
|
&&
|
||||||
tar->CanBuffStack(spell_id, GetLevel(), false) < 0
|
tar->CanBuffStack(spell_id, GetLevel(), true) < 0
|
||||||
) {
|
) {
|
||||||
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanBuffStack.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName());
|
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanBuffStack.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName());
|
||||||
return false;
|
return false;
|
||||||
@@ -11842,7 +11916,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
|
|||||||
return false;
|
return false;
|
||||||
case BotSpellTypes::ResistBuffs:
|
case BotSpellTypes::ResistBuffs:
|
||||||
case BotSpellTypes::PetResistBuffs:
|
case BotSpellTypes::PetResistBuffs:
|
||||||
if (IsResistanceBuffSpell(spell_id)) {
|
if (IsResistanceBuffSpell(spell_id) && !IsEffectInSpell(spell_id, SE_DamageShield)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11966,193 +12040,171 @@ void Bot::SetCastedSpellType(uint16 spell_type) {
|
|||||||
_castedSpellType = spell_type;
|
_castedSpellType = spell_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::DoFaceCheckWithJitter(Mob* tar) {
|
|
||||||
if (!tar) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsMoving()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetCombatJitter();
|
|
||||||
if (!IsFacingMob(tar)) {
|
|
||||||
FaceTarget(tar);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Bot::DoFaceCheckNoJitter(Mob* tar) {
|
|
||||||
if (!tar) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsMoving()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsFacingMob(tar)) {
|
|
||||||
FaceTarget(tar);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Bot::RunToGoalWithJitter(glm::vec3 Goal) {
|
void Bot::RunToGoalWithJitter(glm::vec3 Goal) {
|
||||||
RunTo(Goal.x, Goal.y, Goal.z);
|
RunTo(Goal.x, Goal.y, Goal.z);
|
||||||
SetCombatJitter();
|
SetCombatJitter();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::SetCombatOutOfRangeJitter() {
|
|
||||||
SetCombatOutOfRangeJitterFlag();
|
|
||||||
|
|
||||||
if (RuleI(Bots, MaxJitterTimer) > 0) {
|
|
||||||
m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Bot::SetCombatJitter() {
|
void Bot::SetCombatJitter() {
|
||||||
SetCombatJitterFlag();
|
|
||||||
|
|
||||||
if (RuleI(Bots, MaxJitterTimer) > 0) {
|
if (RuleI(Bots, MaxJitterTimer) > 0) {
|
||||||
m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
|
m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bot::DoCombatPositioning(
|
bool Bot::DoCombatPositioning(const CombatPositioningInput& input)
|
||||||
Mob* tar,
|
{
|
||||||
glm::vec3 Goal,
|
bool adjustment_needed = false;
|
||||||
bool stop_melee_level,
|
bool is_too_close = input.tar_distance < input.melee_distance_min;
|
||||||
float tar_distance,
|
bool los_adjust = !HasRequiredLoSForPositioning(input.tar);
|
||||||
float melee_distance_min,
|
bool behind_mob_set = !input.stop_melee_level &&
|
||||||
float melee_distance,
|
!IsBotRanged() &&
|
||||||
float melee_distance_max,
|
GetBehindMob(); // Don't want casters or ranged to find positions behind the target.
|
||||||
bool behind_mob,
|
bool adjustment_allowed = !IsMoving() &&
|
||||||
bool front_mob
|
m_combat_jitter_timer.Check() &&
|
||||||
) {
|
(!spellend_timer.Enabled() || GetClass() == Class::Bard);
|
||||||
if (!tar->IsFeared()) {
|
|
||||||
bool is_too_close = tar_distance < melee_distance_min;
|
|
||||||
bool los_adjust = !HasRequiredLoSForPositioning(tar);
|
|
||||||
|
|
||||||
if (tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range
|
|
||||||
bool rooted_adjust = tar_distance <= melee_distance_max && HasTargetReflection();
|
|
||||||
|
|
||||||
if (rooted_adjust) {
|
if (!IsMoving() && !IsSitting() && !IsFacingMob(input.tar)) {
|
||||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 1.25f), GetBehindMob(), !GetBehindMob())) {
|
FaceTarget(input.tar);
|
||||||
RunToGoalWithJitter(Goal);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (IsTaunting()) { // Taunting adjustments
|
|
||||||
bool taunting_adjust = (!front_mob || is_too_close || los_adjust);
|
|
||||||
|
|
||||||
if (taunting_adjust) {
|
FindPositionInput find_position_input = {
|
||||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, true)) {
|
.tar = input.tar,
|
||||||
RunToGoalWithJitter(Goal);
|
.distance_min = input.melee_distance_min,
|
||||||
|
.distance_max = input.melee_distance_max,
|
||||||
|
.behind_only = behind_mob_set,
|
||||||
|
.front_only = IsTaunting(),
|
||||||
|
.bypass_los = false,
|
||||||
|
};
|
||||||
|
|
||||||
return;
|
bool is_melee = (!input.stop_melee_level && !IsBotRanged());
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (tar->IsEnraged() && !stop_melee_level && !IsBotRanged()) { // Move non-taunting melee bots behind target during enrage
|
|
||||||
bool enraged_adjust = !behind_mob || is_too_close || los_adjust;
|
|
||||||
|
|
||||||
if (enraged_adjust) {
|
if (input.tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range
|
||||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) {
|
adjustment_needed =
|
||||||
RunToGoalWithJitter(Goal);
|
(input.tar_distance <= input.melee_distance_max) &&
|
||||||
|
HasTargetReflection();
|
||||||
|
|
||||||
return;
|
if (adjustment_needed && adjustment_allowed) {
|
||||||
|
find_position_input.distance_min = input.melee_distance_max + 1;
|
||||||
|
find_position_input.distance_max = input.melee_distance_max * 1.25f;
|
||||||
|
|
||||||
|
PlotBotPositionAroundTarget(find_position_input);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (input.tar->IsFeared()) {
|
||||||
|
adjustment_needed = los_adjust;
|
||||||
|
|
||||||
|
if (adjustment_needed && adjustment_allowed) {
|
||||||
|
find_position_input.distance_min = input.melee_distance_min;
|
||||||
|
find_position_input.distance_max = input.melee_distance;
|
||||||
|
find_position_input.behind_only = false;
|
||||||
|
find_position_input.front_only = false;
|
||||||
|
|
||||||
|
PlotBotPositionAroundTarget(find_position_input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (IsTaunting() || HasTargetReflection()) { // Taunting/Aggro adjustments
|
||||||
|
adjustment_needed =
|
||||||
|
(IsTaunting() && is_too_close) ||
|
||||||
|
los_adjust ||
|
||||||
|
(is_melee && !input.front_mob);
|
||||||
|
|
||||||
|
if (adjustment_needed && adjustment_allowed) {
|
||||||
|
find_position_input.distance_min = input.melee_distance_min;
|
||||||
|
find_position_input.distance_max = input.melee_distance;
|
||||||
|
find_position_input.behind_only = false;
|
||||||
|
find_position_input.front_only = true;
|
||||||
|
|
||||||
|
PlotBotPositionAroundTarget(find_position_input);
|
||||||
}
|
}
|
||||||
else { // Regular adjustments
|
} else {
|
||||||
bool regular_adjust =
|
if (input.tar->IsEnraged() && is_melee) { // Move non-taunting melee bots behind target during enrage
|
||||||
|
adjustment_needed =
|
||||||
|
!behind_mob_set ||
|
||||||
|
is_too_close ||
|
||||||
|
los_adjust;
|
||||||
|
|
||||||
|
if (adjustment_needed && adjustment_allowed) {
|
||||||
|
find_position_input.distance_min = input.melee_distance_min;
|
||||||
|
find_position_input.distance_max = input.melee_distance;
|
||||||
|
find_position_input.behind_only = true;
|
||||||
|
find_position_input.front_only = false;
|
||||||
|
|
||||||
|
PlotBotPositionAroundTarget(find_position_input);
|
||||||
|
}
|
||||||
|
} else { // Regular adjustments
|
||||||
|
adjustment_needed =
|
||||||
is_too_close ||
|
is_too_close ||
|
||||||
los_adjust ||
|
los_adjust ||
|
||||||
(!GetBehindMob() && !front_mob) ||
|
(behind_mob_set && !input.behind_mob);
|
||||||
(GetBehindMob() && !behind_mob);
|
|
||||||
|
|
||||||
if (regular_adjust) {
|
if (adjustment_needed && adjustment_allowed) {
|
||||||
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), !GetBehindMob())) {
|
find_position_input.distance_min = input.melee_distance_min;
|
||||||
RunToGoalWithJitter(Goal);
|
find_position_input.distance_max = input.melee_distance;
|
||||||
|
|
||||||
return;
|
PlotBotPositionAroundTarget(find_position_input);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DoFaceCheckNoJitter(tar);
|
if (!adjustment_needed && IsMoving()) {
|
||||||
|
StopMoving();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Bot::PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behind_only, bool front_only, bool bypass_los) {
|
return adjustment_needed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bot::PlotBotPositionAroundTarget(const FindPositionInput& input) {
|
||||||
bool Result = false;
|
bool Result = false;
|
||||||
|
|
||||||
if (target) {
|
if (input.tar) {
|
||||||
|
glm::vec3 temp_goal(0, 0, input.tar->GetZ());
|
||||||
|
glm::vec3 tar_position(input.tar->GetX(), input.tar->GetY(), input.tar->GetZ());
|
||||||
float look_heading = 0;
|
float look_heading = 0;
|
||||||
|
|
||||||
min_distance = min_distance;
|
|
||||||
max_distance = max_distance;
|
|
||||||
float temp_x = 0;
|
|
||||||
float temp_y = 0;
|
|
||||||
float temp_z = target->GetZ();
|
|
||||||
float best_z = 0;
|
float best_z = 0;
|
||||||
auto offset = GetZOffset();
|
|
||||||
const float tar_x = target->GetX();
|
|
||||||
const float tar_y = target->GetY();
|
|
||||||
float tar_distance = 0;
|
float tar_distance = 0;
|
||||||
|
float desired_angle = 0;
|
||||||
glm::vec3 temp_z_Position;
|
const float offset = GetZOffset();
|
||||||
glm::vec4 temp_m_Position;
|
|
||||||
|
|
||||||
const uint16 max_iterations_allowed = 50;
|
const uint16 max_iterations_allowed = 50;
|
||||||
uint16 counter = 0;
|
uint16 counter = 0;
|
||||||
|
|
||||||
while (counter < max_iterations_allowed) {
|
while (counter < max_iterations_allowed) {
|
||||||
temp_x = tar_x + zone->random.Real(-max_distance, max_distance);
|
temp_goal.x = tar_position.x + zone->random.Real(-input.distance_max, input.distance_max);
|
||||||
temp_y = tar_y + zone->random.Real(-max_distance, max_distance);
|
temp_goal.y = tar_position.y + zone->random.Real(-input.distance_max, input.distance_max);
|
||||||
|
best_z = GetFixedZ(temp_goal);
|
||||||
temp_z_Position.x = temp_x;
|
|
||||||
temp_z_Position.y = temp_y;
|
|
||||||
temp_z_Position.z = temp_z;
|
|
||||||
best_z = GetFixedZ(temp_z_Position);
|
|
||||||
|
|
||||||
if (best_z != BEST_Z_INVALID) {
|
if (best_z != BEST_Z_INVALID) {
|
||||||
temp_z = best_z;
|
temp_goal.z = best_z;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
counter++;
|
counter++;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
temp_m_Position.x = temp_x;
|
tar_distance = Distance(input.tar->GetPosition(), temp_goal);
|
||||||
temp_m_Position.y = temp_y;
|
|
||||||
temp_m_Position.z = temp_z;
|
|
||||||
|
|
||||||
tar_distance = Distance(target->GetPosition(), temp_m_Position);
|
if (tar_distance > input.distance_max || tar_distance < std::max(input.distance_min, (input.distance_max * 0.75f))) {
|
||||||
|
|
||||||
if (tar_distance > max_distance || tar_distance < min_distance) {
|
|
||||||
counter++;
|
counter++;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (front_only && !InFrontMob(target, temp_x, temp_y)) {
|
if (input.front_only && !InFrontMob(input.tar, temp_goal.x, temp_goal.y)) {
|
||||||
counter++;
|
counter++;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (behind_only && !BehindMob(target, temp_x, temp_y)) {
|
else if (input.behind_only && !BehindMob(input.tar, temp_goal.x, temp_goal.y)) {
|
||||||
counter++;
|
counter++;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bypass_los && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(target, temp_x, temp_y, temp_z)) {
|
if (!input.bypass_los && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(input.tar, temp_goal.x, temp_goal.y, temp_goal.z)) {
|
||||||
counter++;
|
counter++;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12161,9 +12213,7 @@ bool Bot::PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Result) {
|
if (Result) {
|
||||||
x_dest = temp_x;
|
RunToGoalWithJitter(temp_goal);
|
||||||
y_dest = temp_y;
|
|
||||||
z_dest = temp_z;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13404,3 +13454,13 @@ bool Bot::IsValidBotStance(uint8 stance) {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Bot::HasControllablePet(uint8 ranks_required) {
|
||||||
|
if (!GetPet()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetClass() != Class::Enchanter ||
|
||||||
|
GetPet()->GetPetType() != petAnimation ||
|
||||||
|
GetAA(aaAnimationEmpathy) >= ranks_required;
|
||||||
|
}
|
||||||
+14
-31
@@ -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,19 +229,10 @@ static std::map<uint16, std::string> botSubType_names = {
|
|||||||
{ CommandedSubTypes::Selo, "Selo" }
|
{ CommandedSubTypes::Selo, "Selo" }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CombatRangeInput {
|
namespace BotAnimEmpathy {
|
||||||
Mob* target;
|
constexpr uint8 Guard = 1;
|
||||||
float target_distance;
|
constexpr uint8 Attack = 2;
|
||||||
bool stop_melee_level;
|
constexpr uint8 BackOff = 3;
|
||||||
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 {
|
||||||
@@ -577,7 +565,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; }
|
||||||
@@ -765,7 +753,7 @@ public:
|
|||||||
static BotSpell GetBestBotSpellForGroupCompleteHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal);
|
static BotSpell GetBestBotSpellForGroupCompleteHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal);
|
||||||
static BotSpell GetBestBotSpellForGroupHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal);
|
static BotSpell GetBestBotSpellForGroupHeal(Bot* caster, Mob* tar, uint16 spell_type = BotSpellTypes::RegularHeal);
|
||||||
|
|
||||||
static Mob* GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_type, bool AE);
|
static Mob* GetFirstIncomingMobToMez(Bot* caster, uint16 spell_id, uint16 spell_type, bool AE);
|
||||||
static BotSpell GetBestBotSpellForMez(Bot* caster, uint16 spell_type = BotSpellTypes::Mez);
|
static BotSpell GetBestBotSpellForMez(Bot* caster, uint16 spell_type = BotSpellTypes::Mez);
|
||||||
static BotSpell GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type = BotSpellTypes::Pet);
|
static BotSpell GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type = BotSpellTypes::Pet);
|
||||||
static std::string GetBotMagicianPetType(Bot* caster);
|
static std::string GetBotMagicianPetType(Bot* caster);
|
||||||
@@ -804,6 +792,7 @@ public:
|
|||||||
EQ::ItemInstance* GetBotItem(uint16 slot_id);
|
EQ::ItemInstance* GetBotItem(uint16 slot_id);
|
||||||
bool GetSpawnStatus() { return _spawnStatus; }
|
bool GetSpawnStatus() { return _spawnStatus; }
|
||||||
uint8 GetPetChooserID() { return _petChooserID; }
|
uint8 GetPetChooserID() { return _petChooserID; }
|
||||||
|
bool HasControllablePet(uint8 ranks_required = 0);
|
||||||
bool IsBotRanged() { return _botRangedSetting; }
|
bool IsBotRanged() { return _botRangedSetting; }
|
||||||
bool IsBotCharmer() { return _botCharmer; }
|
bool IsBotCharmer() { return _botCharmer; }
|
||||||
bool IsBot() const override { return true; }
|
bool IsBot() const override { return true; }
|
||||||
@@ -1102,15 +1091,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);
|
||||||
@@ -1118,18 +1100,21 @@ 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();
|
||||||
|
|
||||||
// Public "Refactor" Methods
|
// Public "Refactor" Methods
|
||||||
static bool CheckCampSpawnConditions(Client* c);
|
static bool CheckCampSpawnConditions(Client* c);
|
||||||
|
static bool CheckHighEnoughLevelForBots(Client* c, uint8 bot_class = Class::None);
|
||||||
|
static bool CheckCreateLimit(Client* c, uint32 bot_count, uint8 bot_class = Class::None);
|
||||||
|
static bool CheckSpawnLimit(Client* c, uint8 bot_class = Class::None);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void BotMeditate(bool is_sitting);
|
void BotMeditate(bool is_sitting);
|
||||||
@@ -1189,8 +1174,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;
|
||||||
|
|||||||
+12
-69
@@ -468,7 +468,7 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas
|
|||||||
|
|
||||||
bool available_flag = false;
|
bool available_flag = false;
|
||||||
|
|
||||||
!database.botdb.QueryNameAvailablity(bot_name, available_flag);
|
!database.botdb.QueryNameAvailability(bot_name, available_flag);
|
||||||
|
|
||||||
if (!available_flag) {
|
if (!available_flag) {
|
||||||
bot_owner->Message(
|
bot_owner->Message(
|
||||||
@@ -517,88 +517,31 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas
|
|||||||
return bot_id;
|
return bot_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto bot_creation_limit = bot_owner->GetBotCreationLimit();
|
if (!Bot::CheckHighEnoughLevelForBots(bot_owner)) {
|
||||||
auto bot_creation_limit_class = bot_owner->GetBotCreationLimit(bot_class);
|
return bot_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Bot::CheckHighEnoughLevelForBots(bot_owner, bot_class)) {
|
||||||
|
return bot_id;
|
||||||
|
}
|
||||||
|
|
||||||
uint32 bot_count = 0;
|
uint32 bot_count = 0;
|
||||||
uint32 bot_class_count = 0;
|
uint32 bot_class_count = 0;
|
||||||
|
|
||||||
if (!database.botdb.QueryBotCount(bot_owner->CharacterID(), bot_class, bot_count, bot_class_count)) {
|
if (!database.botdb.QueryBotCount(bot_owner->CharacterID(), bot_class, bot_count, bot_class_count)) {
|
||||||
bot_owner->Message(Chat::Yellow, "Failed to query bot count.");
|
bot_owner->Message(Chat::Yellow, "Failed to query bot count.");
|
||||||
|
|
||||||
return bot_id;
|
return bot_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) {
|
if (!Bot::CheckCreateLimit(bot_owner, bot_count)) {
|
||||||
std::string message;
|
|
||||||
|
|
||||||
if (bot_creation_limit) {
|
|
||||||
message = fmt::format(
|
|
||||||
"You cannot create anymore than {} bot{}.",
|
|
||||||
bot_creation_limit,
|
|
||||||
bot_creation_limit != 1 ? "s" : ""
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
message = "You cannot create any bots.";
|
|
||||||
}
|
|
||||||
|
|
||||||
bot_owner->Message(Chat::Yellow, message.c_str());
|
|
||||||
return bot_id;
|
return bot_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_class) {
|
if (!Bot::CheckCreateLimit(bot_owner, bot_class_count, bot_class)) {
|
||||||
std::string message;
|
|
||||||
|
|
||||||
if (bot_creation_limit_class) {
|
|
||||||
message = fmt::format(
|
|
||||||
"You cannot create anymore than {} {} bot{}.",
|
|
||||||
bot_creation_limit_class,
|
|
||||||
GetClassIDName(bot_class),
|
|
||||||
bot_creation_limit_class != 1 ? "s" : ""
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
message = fmt::format(
|
|
||||||
"You cannot create any {} bots.",
|
|
||||||
GetClassIDName(bot_class)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bot_owner->Message(Chat::Yellow, message.c_str());
|
|
||||||
return bot_id;
|
return bot_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto bot_character_level = bot_owner->GetBotRequiredLevel();
|
|
||||||
|
|
||||||
if (
|
|
||||||
bot_character_level >= 0 &&
|
|
||||||
bot_owner->GetLevel() < bot_character_level
|
|
||||||
) {
|
|
||||||
bot_owner->Message(
|
|
||||||
Chat::Yellow,
|
|
||||||
fmt::format(
|
|
||||||
"You must be level {} to use bots.",
|
|
||||||
bot_character_level
|
|
||||||
).c_str()
|
|
||||||
);
|
|
||||||
return bot_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto bot_character_level_class = bot_owner->GetBotRequiredLevel(bot_class);
|
|
||||||
|
|
||||||
if (
|
|
||||||
bot_character_level_class >= 0 &&
|
|
||||||
bot_owner->GetLevel() < bot_character_level_class
|
|
||||||
) {
|
|
||||||
bot_owner->Message(
|
|
||||||
Chat::Yellow,
|
|
||||||
fmt::format(
|
|
||||||
"You must be level {} to use {} bots.",
|
|
||||||
bot_character_level_class,
|
|
||||||
GetClassIDName(bot_class)
|
|
||||||
).c_str()
|
|
||||||
);
|
|
||||||
return bot_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
auto my_bot = new Bot(Bot::CreateDefaultNPCTypeStructForBot(bot_name, "", bot_owner->GetLevel(), bot_race, bot_class, bot_gender), bot_owner);
|
auto my_bot = new Bot(Bot::CreateDefaultNPCTypeStructForBot(bot_name, "", bot_owner->GetLevel(), bot_race, bot_class, bot_gender), bot_owner);
|
||||||
|
|
||||||
if (!my_bot->Save()) {
|
if (!my_bot->Save()) {
|
||||||
|
|||||||
+27
-119
@@ -130,7 +130,7 @@ void bot_command_clone(Client *c, const Seperator *sep)
|
|||||||
|
|
||||||
bool available_flag = false;
|
bool available_flag = false;
|
||||||
|
|
||||||
!database.botdb.QueryNameAvailablity(bot_name, available_flag);
|
!database.botdb.QueryNameAvailability(bot_name, available_flag);
|
||||||
|
|
||||||
if (!available_flag) {
|
if (!available_flag) {
|
||||||
c->Message(
|
c->Message(
|
||||||
@@ -144,55 +144,25 @@ void bot_command_clone(Client *c, const Seperator *sep)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto bot_creation_limit = c->GetBotCreationLimit();
|
|
||||||
auto bot_creation_limit_class = c->GetBotCreationLimit(my_bot->GetClass());
|
|
||||||
|
|
||||||
uint32 bot_count = 0;
|
uint32 bot_count = 0;
|
||||||
uint32 bot_class_count = 0;
|
uint32 bot_class_count = 0;
|
||||||
|
|
||||||
if (!database.botdb.QueryBotCount(c->CharacterID(), my_bot->GetClass(), bot_count, bot_class_count)) {
|
if (!database.botdb.QueryBotCount(c->CharacterID(), my_bot->GetClass(), bot_count, bot_class_count)) {
|
||||||
c->Message(Chat::White, "Failed to query bot count.");
|
c->Message(Chat::Yellow, "Failed to query bot count.");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) {
|
if (!Bot::CheckCreateLimit(c, bot_count)) {
|
||||||
std::string message;
|
|
||||||
|
|
||||||
if (bot_creation_limit) {
|
|
||||||
message = fmt::format(
|
|
||||||
"You have reached the maximum limit of {} bot{}.",
|
|
||||||
bot_creation_limit,
|
|
||||||
bot_creation_limit != 1 ? "s" : ""
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
message = "You cannot create any bots.";
|
|
||||||
}
|
|
||||||
|
|
||||||
c->Message(Chat::White, message.c_str());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_class) {
|
if (!Bot::CheckCreateLimit(c, bot_class_count, my_bot->GetClass())) {
|
||||||
std::string message;
|
|
||||||
|
|
||||||
if (bot_creation_limit_class) {
|
|
||||||
message = fmt::format(
|
|
||||||
"You cannot create anymore than {} {} bot{}.",
|
|
||||||
bot_creation_limit_class,
|
|
||||||
GetClassIDName(my_bot->GetClass()),
|
|
||||||
bot_creation_limit_class != 1 ? "s" : ""
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
message = fmt::format(
|
|
||||||
"You cannot create any {} bots.",
|
|
||||||
GetClassIDName(my_bot->GetClass())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
c->Message(Chat::White, message.c_str());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 clone_id = 0;
|
uint32 clone_id = 0;
|
||||||
|
|
||||||
if (!database.botdb.CreateCloneBot(my_bot->GetBotID(), bot_name, clone_id) || !clone_id) {
|
if (!database.botdb.CreateCloneBot(my_bot->GetBotID(), bot_name, clone_id) || !clone_id) {
|
||||||
c->Message(
|
c->Message(
|
||||||
Chat::White,
|
Chat::White,
|
||||||
@@ -205,6 +175,7 @@ void bot_command_clone(Client *c, const Seperator *sep)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int clone_stance = Stance::Passive;
|
int clone_stance = Stance::Passive;
|
||||||
|
|
||||||
if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) {
|
if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) {
|
||||||
c->Message(
|
c->Message(
|
||||||
Chat::White,
|
Chat::White,
|
||||||
@@ -729,6 +700,7 @@ void bot_command_list_bots(Client *c, const Seperator *sep)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int NO_BOT_LIMIT = -1;
|
||||||
bool Account = false;
|
bool Account = false;
|
||||||
int seps = 1;
|
int seps = 1;
|
||||||
uint32 filter_value[FilterCount];
|
uint32 filter_value[FilterCount];
|
||||||
@@ -867,7 +839,7 @@ void bot_command_list_bots(Client *c, const Seperator *sep)
|
|||||||
for (uint8 class_id = Class::Warrior; class_id <= Class::Berserker; class_id++) {
|
for (uint8 class_id = Class::Warrior; class_id <= Class::Berserker; class_id++) {
|
||||||
auto class_creation_limit = c->GetBotCreationLimit(class_id);
|
auto class_creation_limit = c->GetBotCreationLimit(class_id);
|
||||||
|
|
||||||
if (class_creation_limit != overall_bot_creation_limit) {
|
if (class_creation_limit != NO_BOT_LIMIT && class_creation_limit != overall_bot_creation_limit) {
|
||||||
c->Message(
|
c->Message(
|
||||||
Chat::White,
|
Chat::White,
|
||||||
fmt::format(
|
fmt::format(
|
||||||
@@ -938,20 +910,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto bot_character_level = c->GetBotRequiredLevel();
|
if (!Bot::CheckHighEnoughLevelForBots(c)) {
|
||||||
|
|
||||||
if (
|
|
||||||
bot_character_level >= 0 &&
|
|
||||||
c->GetLevel() < bot_character_level &&
|
|
||||||
!c->GetGM()
|
|
||||||
) {
|
|
||||||
c->Message(
|
|
||||||
Chat::White,
|
|
||||||
fmt::format(
|
|
||||||
"You must be level {} to spawn bots.",
|
|
||||||
bot_character_level
|
|
||||||
).c_str()
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -959,27 +918,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto bot_spawn_limit = c->GetBotSpawnLimit();
|
if (!Bot::CheckSpawnLimit(c)) {
|
||||||
auto spawned_bot_count = Bot::SpawnedBotCount(c->CharacterID());
|
|
||||||
|
|
||||||
if (
|
|
||||||
bot_spawn_limit >= 0 &&
|
|
||||||
spawned_bot_count >= bot_spawn_limit &&
|
|
||||||
!c->GetGM()
|
|
||||||
) {
|
|
||||||
std::string message;
|
|
||||||
|
|
||||||
if (bot_spawn_limit) {
|
|
||||||
message = fmt::format(
|
|
||||||
"You cannot have more than {} spawned bot{}.",
|
|
||||||
bot_spawn_limit,
|
|
||||||
bot_spawn_limit != 1 ? "s" : ""
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
message = "You are not currently allowed to spawn any bots.";
|
|
||||||
}
|
|
||||||
|
|
||||||
c->Message(Chat::White, message.c_str());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1004,52 +943,6 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto bot_spawn_limit_class = c->GetBotSpawnLimit(bot_class);
|
|
||||||
auto spawned_bot_count_class = Bot::SpawnedBotCount(c->CharacterID(), bot_class);
|
|
||||||
|
|
||||||
if (
|
|
||||||
bot_spawn_limit_class >= 0 &&
|
|
||||||
spawned_bot_count_class >= bot_spawn_limit_class &&
|
|
||||||
!c->GetGM()
|
|
||||||
) {
|
|
||||||
std::string message;
|
|
||||||
|
|
||||||
if (bot_spawn_limit_class) {
|
|
||||||
message = fmt::format(
|
|
||||||
"You cannot have more than {} spawned {} bot{}.",
|
|
||||||
bot_spawn_limit_class,
|
|
||||||
GetClassIDName(bot_class),
|
|
||||||
bot_spawn_limit_class != 1 ? "s" : ""
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
message = fmt::format(
|
|
||||||
"You are not currently allowed to spawn any {} bots.",
|
|
||||||
GetClassIDName(bot_class)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
c->Message(Chat::White, message.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto bot_character_level_class = c->GetBotRequiredLevel(bot_class);
|
|
||||||
|
|
||||||
if (
|
|
||||||
bot_character_level_class >= 0 &&
|
|
||||||
c->GetLevel() < bot_character_level_class &&
|
|
||||||
!c->GetGM()
|
|
||||||
) {
|
|
||||||
c->Message(
|
|
||||||
Chat::White,
|
|
||||||
fmt::format(
|
|
||||||
"You must be level {} to spawn {} bots.",
|
|
||||||
bot_character_level_class,
|
|
||||||
GetClassIDName(bot_class)
|
|
||||||
).c_str()
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bot_id) {
|
if (!bot_id) {
|
||||||
c->Message(
|
c->Message(
|
||||||
Chat::White,
|
Chat::White,
|
||||||
@@ -1061,6 +954,14 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Bot::CheckHighEnoughLevelForBots(c, bot_class)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Bot::CheckSpawnLimit(c, bot_class)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (entity_list.GetMobByBotID(bot_id)) {
|
if (entity_list.GetMobByBotID(bot_id)) {
|
||||||
c->Message(
|
c->Message(
|
||||||
Chat::White,
|
Chat::White,
|
||||||
@@ -1069,6 +970,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
|||||||
bot_name
|
bot_name
|
||||||
).c_str()
|
).c_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1083,6 +985,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
|||||||
bot_id
|
bot_id
|
||||||
).c_str()
|
).c_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1097,6 +1000,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
|||||||
);
|
);
|
||||||
|
|
||||||
safe_delete(my_bot);
|
safe_delete(my_bot);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1121,6 +1025,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
|
|||||||
};
|
};
|
||||||
|
|
||||||
uint8 message_index = 0;
|
uint8 message_index = 0;
|
||||||
|
|
||||||
if (c->GetBotOption(Client::booSpawnMessageClassSpecific)) {
|
if (c->GetBotOption(Client::booSpawnMessageClassSpecific)) {
|
||||||
message_index = VALIDATECLASSID(my_bot->GetClass());
|
message_index = VALIDATECLASSID(my_bot->GetClass());
|
||||||
}
|
}
|
||||||
@@ -1560,8 +1465,11 @@ void bot_command_summon(Client *c, const Seperator *sep)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bot_iter->HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||||
bot_iter->GetPet()->WipeHateList();
|
bot_iter->GetPet()->WipeHateList();
|
||||||
bot_iter->GetPet()->SetTarget(nullptr);
|
bot_iter->GetPet()->SetTarget(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
bot_iter->GetPet()->Teleport(c->GetPosition());
|
bot_iter->GetPet()->Teleport(c->GetPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-59
@@ -5,8 +5,8 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
|||||||
if (helper_command_alias_fail(c, "bot_command_pull", sep->arg[0], "pull")) {
|
if (helper_command_alias_fail(c, "bot_command_pull", sep->arg[0], "pull")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (helper_is_help_or_usage(sep->arg[1])) {
|
|
||||||
|
|
||||||
|
if (helper_is_help_or_usage(sep->arg[1])) {
|
||||||
c->Message(Chat::White, "usage: <enemy_target> %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]);
|
c->Message(Chat::White, "usage: <enemy_target> %s ([actionable: target | byname | ownergroup | ownerraid | targetgroup | namesgroup | healrotationtargets | mmr | byclass | byrace | spawned] ([actionable_name]))", sep->arg[0]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!target_mob ||
|
!target_mob ||
|
||||||
target_mob == c ||
|
target_mob->IsOfClientBotMerc() ||
|
||||||
!c->IsAttackAllowed(target_mob)
|
!c->IsAttackAllowed(target_mob)
|
||||||
) {
|
) {
|
||||||
c->Message(Chat::White, "Your current target is not attackable!");
|
c->Message(Chat::White, "Your current target is not attackable!");
|
||||||
@@ -55,12 +55,16 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (target_mob->IsNPC() && target_mob->GetHateList().size()) {
|
if (target_mob->IsNPC() && target_mob->GetHateList().size()) {
|
||||||
|
|
||||||
c->Message(Chat::White, "Your current target is already engaged!");
|
c->Message(Chat::White, "Your current target is already engaged!");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bot* bot_puller = nullptr;
|
Bot* bot_puller = nullptr;
|
||||||
|
Bot* backup_bot_puller = nullptr;
|
||||||
|
Bot* alternate_bot_puller = nullptr;
|
||||||
|
bool backup_puller_found = false;
|
||||||
|
bool alternate_puller_found = false;
|
||||||
|
|
||||||
for (auto bot_iter : sbl) {
|
for (auto bot_iter : sbl) {
|
||||||
if (!bot_iter->ValidStateCheck(c)) {
|
if (!bot_iter->ValidStateCheck(c)) {
|
||||||
@@ -72,71 +76,36 @@ void bot_command_pull(Client *c, const Seperator *sep)
|
|||||||
case Class::Monk:
|
case Class::Monk:
|
||||||
case Class::Bard:
|
case Class::Bard:
|
||||||
case Class::Ranger:
|
case Class::Ranger:
|
||||||
bot_puller = bot_iter;
|
bot_iter->SetPullFlag();
|
||||||
|
|
||||||
|
return;
|
||||||
|
default:
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!backup_puller_found) {
|
||||||
|
switch (bot_iter->GetClass()) {
|
||||||
case Class::Warrior:
|
case Class::Warrior:
|
||||||
case Class::ShadowKnight:
|
case Class::ShadowKnight:
|
||||||
case Class::Paladin:
|
case Class::Paladin:
|
||||||
case Class::Berserker:
|
case Class::Berserker:
|
||||||
case Class::Beastlord:
|
case Class::Beastlord:
|
||||||
if (!bot_puller) {
|
backup_bot_puller = bot_iter;
|
||||||
|
backup_puller_found = true;
|
||||||
bot_puller = bot_iter;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (bot_puller->GetClass()) {
|
|
||||||
case Class::Druid:
|
|
||||||
case Class::Shaman:
|
|
||||||
case Class::Cleric:
|
|
||||||
case Class::Wizard:
|
|
||||||
case Class::Necromancer:
|
|
||||||
case Class::Magician:
|
|
||||||
case Class::Enchanter:
|
|
||||||
bot_puller = bot_iter;
|
|
||||||
default:
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
case Class::Druid:
|
|
||||||
case Class::Shaman:
|
|
||||||
case Class::Cleric:
|
|
||||||
if (!bot_puller) {
|
|
||||||
|
|
||||||
bot_puller = bot_iter;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (bot_puller->GetClass()) {
|
|
||||||
case Class::Wizard:
|
|
||||||
case Class::Necromancer:
|
|
||||||
case Class::Magician:
|
|
||||||
case Class::Enchanter:
|
|
||||||
bot_puller = bot_iter;
|
|
||||||
default:
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
case Class::Wizard:
|
|
||||||
case Class::Necromancer:
|
|
||||||
case Class::Magician:
|
|
||||||
case Class::Enchanter:
|
|
||||||
if (!bot_puller) {
|
|
||||||
bot_puller = bot_iter;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
default:
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bot_puller = bot_iter;
|
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!backup_puller_found && !alternate_puller_found) {
|
||||||
|
alternate_bot_puller = bot_iter;
|
||||||
|
alternate_puller_found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bot_puller = backup_bot_puller ? backup_bot_puller : alternate_bot_puller;
|
||||||
|
|
||||||
if (bot_puller) {
|
if (bot_puller) {
|
||||||
bot_puller->SetPullFlag();
|
bot_puller->SetPullFlag();
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ void bot_command_release(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());
|
||||||
for (auto bot_iter : sbl) {
|
for (auto bot_iter : sbl) {
|
||||||
bot_iter->WipeHateList();
|
bot_iter->WipeHateList();
|
||||||
|
|
||||||
|
if (bot_iter->GetPet() && bot_iter->HasControllablePet(BotAnimEmpathy::BackOff)) {
|
||||||
|
bot_iter->GetPet()->WipeHateList();
|
||||||
|
bot_iter->GetPet()->SetTarget(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
bot_iter->SetPauseAI(false);
|
bot_iter->SetPauseAI(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ bool BotDatabase::LoadBotSpellCastingChances()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BotDatabase::QueryNameAvailablity(const std::string& bot_name, bool& available_flag)
|
bool BotDatabase::QueryNameAvailability(const std::string& bot_name, bool& available_flag)
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
bot_name.empty() ||
|
bot_name.empty() ||
|
||||||
@@ -207,7 +207,7 @@ bool BotDatabase::QueryBotCount(const uint32 owner_id, int class_id, uint32& bot
|
|||||||
bot_count = BotDataRepository::Count(
|
bot_count = BotDataRepository::Count(
|
||||||
database,
|
database,
|
||||||
fmt::format(
|
fmt::format(
|
||||||
"`owner_id` = {}",
|
"`owner_id` = {} AND `name` NOT LIKE '%-deleted-%'",
|
||||||
owner_id
|
owner_id
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -216,7 +216,7 @@ bool BotDatabase::QueryBotCount(const uint32 owner_id, int class_id, uint32& bot
|
|||||||
bot_class_count = BotDataRepository::Count(
|
bot_class_count = BotDataRepository::Count(
|
||||||
database,
|
database,
|
||||||
fmt::format(
|
fmt::format(
|
||||||
"`owner_id` = {} AND `class` = {}",
|
"`owner_id` = {} AND `class` = {} AND `name` NOT LIKE '%-deleted-%'",
|
||||||
owner_id,
|
owner_id,
|
||||||
class_id
|
class_id
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ public:
|
|||||||
|
|
||||||
|
|
||||||
/* Bot functions */
|
/* Bot functions */
|
||||||
bool QueryNameAvailablity(const std::string& bot_name, bool& available_flag);
|
bool QueryNameAvailability(const std::string& bot_name, bool& available_flag);
|
||||||
bool QueryBotCount(const uint32 owner_id, int class_id, uint32& bot_count, uint32& bot_class_count);
|
bool QueryBotCount(const uint32 owner_id, int class_id, uint32& bot_count, uint32& bot_class_count);
|
||||||
bool LoadBotsList(const uint32 owner_id, std::list<BotsAvailableList>& bots_list, bool by_account = false);
|
bool LoadBotsList(const uint32 owner_id, std::list<BotsAvailableList>& bots_list, bool by_account = false);
|
||||||
|
|
||||||
|
|||||||
+38
-2
@@ -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>
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ struct BotSpellSetting {
|
|||||||
|
|
||||||
struct BotSpells {
|
struct BotSpells {
|
||||||
uint32 type; // 0 = never, must be one (and only one) of the defined values
|
uint32 type; // 0 = never, must be one (and only one) of the defined values
|
||||||
int16 spellid; // <= 0 = no spell
|
uint16 spellid; // <= 0 = no spell
|
||||||
int16 manacost; // -1 = use spdat, -2 = no cast time
|
int16 manacost; // -1 = use spdat, -2 = no cast time
|
||||||
uint32 time_cancast; // when we can cast this spell next
|
uint32 time_cancast; // when we can cast this spell next
|
||||||
int32 recast_delay;
|
int32 recast_delay;
|
||||||
@@ -85,7 +86,7 @@ struct BotSpells {
|
|||||||
struct BotSpells_wIndex {
|
struct BotSpells_wIndex {
|
||||||
uint32 index; //index of AIBot_spells
|
uint32 index; //index of AIBot_spells
|
||||||
uint32 type; // 0 = never, must be one (and only one) of the defined values
|
uint32 type; // 0 = never, must be one (and only one) of the defined values
|
||||||
int16 spellid; // <= 0 = no spell
|
uint16 spellid; // <= 0 = no spell
|
||||||
int16 manacost; // -1 = use spdat, -2 = no cast time
|
int16 manacost; // -1 = use spdat, -2 = no cast time
|
||||||
uint32 time_cancast; // when we can cast this spell next
|
uint32 time_cancast; // when we can cast this spell next
|
||||||
int32 recast_delay;
|
int32 recast_delay;
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1459,7 +1459,7 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* caster, uint16 spell_type) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_type, bool AE) {
|
Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, uint16 spell_id, uint16 spell_type, bool AE) {
|
||||||
Mob* result = nullptr;
|
Mob* result = nullptr;
|
||||||
|
|
||||||
if (caster && caster->GetOwner()) {
|
if (caster && caster->GetOwner()) {
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ inline void SetupStateZone()
|
|||||||
SetupZone("soldungb");
|
SetupZone("soldungb");
|
||||||
zone->Process();
|
zone->Process();
|
||||||
// depop the zone controller
|
// depop the zone controller
|
||||||
entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID)->Depop();
|
auto controller = entity_list.GetNPCByNPCTypeID(ZONE_CONTROLLER_NPC_ID);
|
||||||
|
if (controller != nullptr) {
|
||||||
|
controller->Depop();
|
||||||
|
}
|
||||||
|
|
||||||
entity_list.MobProcess(); // process the depop
|
entity_list.MobProcess(); // process the depop
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,8 +356,12 @@ inline void TestSpawns()
|
|||||||
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == 115;
|
bool condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == entries;
|
||||||
RunTest("Spawns > All NPC's killed (0 NPCs) (115 Corpses)", true, condition);
|
RunTest(
|
||||||
|
fmt::format("Spawns > All NPC's killed (0 NPCs) ([{}] Corpses)", entries),
|
||||||
|
true,
|
||||||
|
condition
|
||||||
|
);
|
||||||
|
|
||||||
std::vector<uint32_t> spawn2_ids = {};
|
std::vector<uint32_t> spawn2_ids = {};
|
||||||
|
|
||||||
@@ -377,11 +385,15 @@ inline void TestSpawns()
|
|||||||
zone->Shutdown();
|
zone->Shutdown();
|
||||||
SetupStateZone();
|
SetupStateZone();
|
||||||
|
|
||||||
condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == 115;
|
condition = (int) entity_list.GetNPCList().size() == 0 && (int) entity_list.GetCorpseList().size() == entries;
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
PrintEntityCounts();
|
PrintEntityCounts();
|
||||||
}
|
}
|
||||||
RunTest("Spawns > After restore (0 NPCs) (115 Corpses)", true, condition);
|
RunTest(
|
||||||
|
fmt::format("Spawns > After restore (0 NPCs) ([{}] Corpses)", entries),
|
||||||
|
true,
|
||||||
|
condition
|
||||||
|
);
|
||||||
|
|
||||||
for (auto &e: entity_list.GetCorpseList()) {
|
for (auto &e: entity_list.GetCorpseList()) {
|
||||||
auto c = e.second;
|
auto c = e.second;
|
||||||
@@ -405,11 +417,15 @@ inline void TestSpawns()
|
|||||||
|
|
||||||
zone->Process();
|
zone->Process();
|
||||||
|
|
||||||
condition = (int) entity_list.GetNPCList().size() == 115 && (int) entity_list.GetCorpseList().size() == 115;
|
condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == entries;
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
PrintEntityCounts();
|
PrintEntityCounts();
|
||||||
}
|
}
|
||||||
RunTest("Spawns > After respawn (115 NPCs) (115 Corpses)", true, condition);
|
RunTest(
|
||||||
|
fmt::format("Spawns > After respawn ([{}] NPCs) ([{}] Corpses)", entries, entries),
|
||||||
|
true,
|
||||||
|
condition
|
||||||
|
);
|
||||||
|
|
||||||
for (auto &c: entity_list.GetCorpseList()) {
|
for (auto &c: entity_list.GetCorpseList()) {
|
||||||
c.second->DepopNPCCorpse();
|
c.second->DepopNPCCorpse();
|
||||||
@@ -417,11 +433,15 @@ inline void TestSpawns()
|
|||||||
|
|
||||||
entity_list.CorpseProcess();
|
entity_list.CorpseProcess();
|
||||||
|
|
||||||
condition = (int) entity_list.GetNPCList().size() == 115 && (int) entity_list.GetCorpseList().size() == 0;
|
condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == 0;
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
PrintEntityCounts();
|
PrintEntityCounts();
|
||||||
}
|
}
|
||||||
RunTest("Spawns > After respawn (115 NPCs) (0 Corpses)", true, condition);
|
RunTest(
|
||||||
|
fmt::format("Spawns > After respawn ([{}] NPCs) (0 Corpses)", entries),
|
||||||
|
true,
|
||||||
|
condition
|
||||||
|
);
|
||||||
|
|
||||||
// lets set NPC's up with a predictable loottable for testing
|
// lets set NPC's up with a predictable loottable for testing
|
||||||
uint32_t loottable_id = SeedLootTable();
|
uint32_t loottable_id = SeedLootTable();
|
||||||
@@ -462,20 +482,28 @@ inline void TestSpawns()
|
|||||||
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
|
||||||
}
|
}
|
||||||
|
|
||||||
condition = (int) entity_list.GetNPCList().size() == 105 && (int) entity_list.GetCorpseList().size() == 10;
|
condition = (int) entity_list.GetNPCList().size() == (entries - 10) && (int) entity_list.GetCorpseList().size() == 10;
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
PrintEntityCounts();
|
PrintEntityCounts();
|
||||||
}
|
}
|
||||||
RunTest("Spawns > Kill 10 NPC's before save/restore (105 NPCs) (10 Corpses)", true, condition);
|
RunTest(
|
||||||
|
fmt::format("Spawns > Kill 10 NPC's before save/restore ([{}] NPCs) (10 Corpses)", (entries - 10)),
|
||||||
|
true,
|
||||||
|
condition
|
||||||
|
);
|
||||||
|
|
||||||
zone->Shutdown();
|
zone->Shutdown();
|
||||||
SetupStateZone();
|
SetupStateZone();
|
||||||
|
|
||||||
condition = (int) entity_list.GetNPCList().size() == 105 && (int) entity_list.GetCorpseList().size() == 10;
|
condition = (int) entity_list.GetNPCList().size() == (entries - 10) && (int) entity_list.GetCorpseList().size() == 10;
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
PrintEntityCounts();
|
PrintEntityCounts();
|
||||||
}
|
}
|
||||||
RunTest("Spawns > After restore (105 NPCs) (10 Corpses)", true, condition);
|
RunTest(
|
||||||
|
fmt::format("Spawns > After restore ([{}] NPCs) (10 Corpses)", (entries - 10)),
|
||||||
|
true,
|
||||||
|
condition
|
||||||
|
);
|
||||||
|
|
||||||
// validate that all corpses and npc's have cloak of flames
|
// validate that all corpses and npc's have cloak of flames
|
||||||
bool test_failed = false;
|
bool test_failed = false;
|
||||||
@@ -572,6 +600,14 @@ inline void TestSpawns()
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
int max_respawn = 0;
|
||||||
|
const auto& l = RespawnTimesRepository::All(database);
|
||||||
|
for (const auto& e : l) {
|
||||||
|
if (e.duration > max_respawn) {
|
||||||
|
max_respawn = e.duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
entity_list.MobProcess();
|
entity_list.MobProcess();
|
||||||
|
|
||||||
zone->Process();
|
zone->Process();
|
||||||
@@ -587,16 +623,20 @@ inline void TestSpawns()
|
|||||||
npc->SetEntityVariable("previously_spawned", "true");
|
npc->SetEntityVariable("previously_spawned", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer::RollForward(302401); // longest respawn time in zone
|
Timer::RollForward(max_respawn); // longest respawn time in zone
|
||||||
zone->Process();
|
zone->Process();
|
||||||
entity_list.MobProcess(); // processing depops
|
entity_list.MobProcess(); // processing depops
|
||||||
|
|
||||||
condition = (int) entity_list.GetNPCList().size() == 115 && (int) entity_list.GetCorpseList().size() == 10;
|
condition = (int) entity_list.GetNPCList().size() == entries && (int) entity_list.GetCorpseList().size() == 10;
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
PrintEntityCounts();
|
PrintEntityCounts();
|
||||||
PrintZoneNpcs();
|
PrintZoneNpcs();
|
||||||
}
|
}
|
||||||
RunTest("Spawns > After respawn, ensure we have expected entity counts (115 NPCs) (10 Corpses)", true, condition);
|
RunTest(
|
||||||
|
fmt::format("Spawns > After respawn, ensure we have expected entity counts ([{}] NPCs) (10 Corpses)", entries),
|
||||||
|
true,
|
||||||
|
condition
|
||||||
|
);
|
||||||
|
|
||||||
entity_list.MobProcess(); // processing depops
|
entity_list.MobProcess(); // processing depops
|
||||||
|
|
||||||
|
|||||||
@@ -995,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;
|
||||||
@@ -1022,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);
|
||||||
|
|
||||||
@@ -1105,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1209,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();
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-4
@@ -73,6 +73,7 @@ namespace EQ
|
|||||||
#include "../common/guild_base.h"
|
#include "../common/guild_base.h"
|
||||||
#include "../common/repositories/buyer_buy_lines_repository.h"
|
#include "../common/repositories/buyer_buy_lines_repository.h"
|
||||||
#include "../common/repositories/character_evolving_items_repository.h"
|
#include "../common/repositories/character_evolving_items_repository.h"
|
||||||
|
#include "../common/repositories/player_titlesets_repository.h"
|
||||||
|
|
||||||
#include "bot_structs.h"
|
#include "bot_structs.h"
|
||||||
|
|
||||||
@@ -483,6 +484,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); }
|
||||||
@@ -1252,9 +1256,10 @@ public:
|
|||||||
void ResetAllCastbarCooldowns();
|
void ResetAllCastbarCooldowns();
|
||||||
void ResetCastbarCooldownBySpellID(uint32 spell_id);
|
void ResetCastbarCooldownBySpellID(uint32 spell_id);
|
||||||
|
|
||||||
bool CheckTitle(int titleset);
|
bool CheckTitle(int title_set);
|
||||||
void EnableTitle(int titleset);
|
void EnableTitle(int title_set, bool insert = true);
|
||||||
void RemoveTitle(int titleset);
|
const std::vector<PlayerTitlesetsRepository::PlayerTitlesets>& GetTitles() { return m_player_title_sets; };
|
||||||
|
void RemoveTitle(int title_set);
|
||||||
|
|
||||||
void EnteringMessages(Client* client);
|
void EnteringMessages(Client* client);
|
||||||
void SendRules();
|
void SendRules();
|
||||||
@@ -2077,7 +2082,8 @@ private:
|
|||||||
uint16 trader_id;
|
uint16 trader_id;
|
||||||
uint16 customer_id;
|
uint16 customer_id;
|
||||||
uint32 account_creation;
|
uint32 account_creation;
|
||||||
uint8 firstlogon;
|
bool first_login;
|
||||||
|
bool ingame;
|
||||||
uint32 mercid; // current merc
|
uint32 mercid; // current merc
|
||||||
uint8 mercSlot; // selected merc slot
|
uint8 mercSlot; // selected merc slot
|
||||||
time_t m_trader_transaction_date;
|
time_t m_trader_transaction_date;
|
||||||
@@ -2254,6 +2260,7 @@ private:
|
|||||||
bool m_exp_enabled;
|
bool m_exp_enabled;
|
||||||
|
|
||||||
std::vector<EXPModifier> m_exp_modifiers;
|
std::vector<EXPModifier> m_exp_modifiers;
|
||||||
|
std::vector<PlayerTitlesetsRepository::PlayerTitlesets> m_player_title_sets;
|
||||||
|
|
||||||
//Anti Spam Stuff
|
//Anti Spam Stuff
|
||||||
Timer *KarmaUpdateTimer;
|
Timer *KarmaUpdateTimer;
|
||||||
|
|||||||
+30
-36
@@ -1,6 +1,8 @@
|
|||||||
#include "bot.h"
|
#include "bot.h"
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
|
|
||||||
|
#define NO_BOT_LIMIT -1;
|
||||||
|
|
||||||
bool Client::GetBotOption(BotOwnerOption boo) const {
|
bool Client::GetBotOption(BotOwnerOption boo) const {
|
||||||
if (boo < _booCount) {
|
if (boo < _booCount) {
|
||||||
return bot_owner_options[boo];
|
return bot_owner_options[boo];
|
||||||
@@ -18,25 +20,25 @@ void Client::SetBotOption(BotOwnerOption boo, bool flag) {
|
|||||||
uint32 Client::GetBotCreationLimit(uint8 class_id) {
|
uint32 Client::GetBotCreationLimit(uint8 class_id) {
|
||||||
uint32 bot_creation_limit = RuleI(Bots, CreationLimit);
|
uint32 bot_creation_limit = RuleI(Bots, CreationLimit);
|
||||||
|
|
||||||
if (Admin() >= RuleI(Bots, MinStatusToBypassCreateLimit)) {
|
if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassCreateLimit)) {
|
||||||
return RuleI(Bots, MinStatusBypassCreateLimit);
|
return RuleI(Bots, MinStatusBypassCreateLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto bucket_name = fmt::format(
|
const auto bucket_name = fmt::format(
|
||||||
"bot_creation_limit{}",
|
"bot_creation_limit{}",
|
||||||
(
|
|
||||||
class_id && IsPlayerClass(class_id) ?
|
class_id && IsPlayerClass(class_id) ?
|
||||||
fmt::format(
|
fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) :
|
||||||
"_{}",
|
|
||||||
Strings::ToLower(GetClassIDName(class_id))
|
|
||||||
) :
|
|
||||||
""
|
""
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
auto bucket_value = GetBucket(bucket_name);
|
auto bucket_value = GetBucket(bucket_name);
|
||||||
|
|
||||||
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
||||||
bot_creation_limit = Strings::ToUnsignedInt(bucket_value);
|
bot_creation_limit = Strings::ToInt(bucket_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (class_id && bucket_value.empty()) {
|
||||||
|
bot_creation_limit = NO_BOT_LIMIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
return bot_creation_limit;
|
return bot_creation_limit;
|
||||||
@@ -45,61 +47,53 @@ uint32 Client::GetBotCreationLimit(uint8 class_id) {
|
|||||||
int Client::GetBotRequiredLevel(uint8 class_id) {
|
int Client::GetBotRequiredLevel(uint8 class_id) {
|
||||||
int bot_character_level = RuleI(Bots, BotCharacterLevel);
|
int bot_character_level = RuleI(Bots, BotCharacterLevel);
|
||||||
|
|
||||||
|
if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassBotLevelRequirement)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
const auto bucket_name = fmt::format(
|
const auto bucket_name = fmt::format(
|
||||||
"bot_required_level{}",
|
"bot_required_level{}",
|
||||||
(
|
|
||||||
class_id && IsPlayerClass(class_id) ?
|
class_id && IsPlayerClass(class_id) ?
|
||||||
fmt::format(
|
fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) :
|
||||||
"_{}",
|
|
||||||
Strings::ToLower(GetClassIDName(class_id))
|
|
||||||
) :
|
|
||||||
""
|
""
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
auto bucket_value = GetBucket(bucket_name);
|
auto bucket_value = GetBucket(bucket_name);
|
||||||
|
|
||||||
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
||||||
bot_character_level = Strings::ToInt(bucket_value);
|
bot_character_level = Strings::ToInt(bucket_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (class_id && bucket_value.empty()) {
|
||||||
|
bot_character_level = NO_BOT_LIMIT;
|
||||||
|
}
|
||||||
|
|
||||||
return bot_character_level;
|
return bot_character_level;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Client::GetBotSpawnLimit(uint8 class_id) {
|
int Client::GetBotSpawnLimit(uint8 class_id)
|
||||||
|
{
|
||||||
int bot_spawn_limit = RuleI(Bots, SpawnLimit);
|
int bot_spawn_limit = RuleI(Bots, SpawnLimit);
|
||||||
|
|
||||||
if (Admin() >= RuleI(Bots, MinStatusToBypassSpawnLimit)) {
|
if (GetGM() && Admin() >= RuleI(Bots, MinStatusToBypassSpawnLimit)) {
|
||||||
return RuleI(Bots, MinStatusBypassSpawnLimit);
|
return RuleI(Bots, MinStatusBypassSpawnLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto bucket_name = fmt::format(
|
const auto bucket_name = fmt::format(
|
||||||
"bot_spawn_limit{}",
|
"bot_spawn_limit{}",
|
||||||
(
|
|
||||||
class_id && IsPlayerClass(class_id) ?
|
class_id && IsPlayerClass(class_id) ?
|
||||||
fmt::format(
|
fmt::format("_{}", Strings::ToLower(GetClassIDName(class_id))) :
|
||||||
"_{}",
|
|
||||||
Strings::ToLower(GetClassIDName(class_id))
|
|
||||||
) :
|
|
||||||
""
|
""
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
auto bucket_value = GetBucket(bucket_name);
|
auto bucket_value = GetBucket(bucket_name);
|
||||||
|
|
||||||
if (class_id && !bot_spawn_limit && bucket_value.empty()) {
|
|
||||||
const auto new_bucket_name = "bot_spawn_limit";
|
|
||||||
|
|
||||||
bucket_value = GetBucket(new_bucket_name);
|
|
||||||
|
|
||||||
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
||||||
bot_spawn_limit = Strings::ToInt(bucket_value);
|
bot_spawn_limit = Strings::ToInt(bucket_value);
|
||||||
|
|
||||||
return bot_spawn_limit;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bucket_value.empty() && Strings::IsNumber(bucket_value)) {
|
if (class_id && bucket_value.empty()) {
|
||||||
bot_spawn_limit = Strings::ToInt(bucket_value);
|
return NO_BOT_LIMIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RuleB(Bots, QuestableSpawnLimit)) {
|
if (RuleB(Bots, QuestableSpawnLimit)) {
|
||||||
@@ -111,14 +105,13 @@ int Client::GetBotSpawnLimit(uint8 class_id) {
|
|||||||
|
|
||||||
auto results = database.QueryDatabase(query); // use 'database' for non-bot table calls
|
auto results = database.QueryDatabase(query); // use 'database' for non-bot table calls
|
||||||
|
|
||||||
if (!results.Success() || !results.RowCount()) {
|
if (results.Success() && results.RowCount()) {
|
||||||
return bot_spawn_limit;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto row = results.begin();
|
auto row = results.begin();
|
||||||
bot_spawn_limit = Strings::ToInt(row[0]);
|
bot_spawn_limit = Strings::ToInt(row[0]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!class_id) {
|
||||||
const auto &zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ",");
|
const auto &zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ",");
|
||||||
|
|
||||||
if (!zones_list.empty()) {
|
if (!zones_list.empty()) {
|
||||||
@@ -162,6 +155,7 @@ int Client::GetBotSpawnLimit(uint8 class_id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return bot_spawn_limit;
|
return bot_spawn_limit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,16 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!evolving_items_manager.GetEvolvingItemsCache().contains(inst->GetID())) {
|
||||||
|
LogEvolveItem(
|
||||||
|
"Character ID {} has an evolving item that is not found in the db. Please check your "
|
||||||
|
"items_evolving_details table for item id {}",
|
||||||
|
CharacterID(),
|
||||||
|
inst->GetID()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto const type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).type;
|
auto const type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).type;
|
||||||
auto const sub_type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).sub_type;
|
auto const sub_type = evolving_items_manager.GetEvolvingItemsCache().at(inst->GetID()).sub_type;
|
||||||
|
|
||||||
@@ -283,24 +293,31 @@ void Client::DoEvolveItemDisplayFinalResult(const EQApplicationPacket *app)
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<EQ::ItemInstance> const inst(database.CreateItem(item_id));
|
std::unique_ptr<EQ::ItemInstance> const inst(database.CreateItem(item_id));
|
||||||
|
if (!inst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
LogEvolveItemDetail(
|
LogEvolveItemDetail(
|
||||||
"Character ID <green>[{}] requested to view final evolve item id <yellow>[{}] for evolve item id <yellow>[{}]",
|
"Character ID <green>[{}] requested to view final evolve item id <yellow>[{}] for evolve item id <yellow>[{}]",
|
||||||
CharacterID(),
|
CharacterID(),
|
||||||
item_id,
|
item_id,
|
||||||
evolving_items_manager.GetFirstItemInLoreGroupByItemID(item_id));
|
evolving_items_manager.GetFirstItemInLoreGroupByItemID(item_id)
|
||||||
|
);
|
||||||
|
|
||||||
inst->SetEvolveProgression(100);
|
inst->SetEvolveProgression(100);
|
||||||
|
|
||||||
if (inst) {
|
|
||||||
LogEvolveItemDetail(
|
LogEvolveItemDetail(
|
||||||
"Sending final result for item id <yellow>[{}] to Character ID <green>[{}]", item_id, CharacterID());
|
"Sending final result for item id <yellow>[{}] to Character ID <green>[{}]", item_id, CharacterID()
|
||||||
|
);
|
||||||
SendItemPacket(0, inst.get(), ItemPacketViewLink);
|
SendItemPacket(0, inst.get(), ItemPacketViewLink);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst)
|
bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst)
|
||||||
{
|
{
|
||||||
|
if (!inst) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (inst.GetEvolveProgression() < 100 || inst.GetEvolveLvl() == inst.GetMaxEvolveLvl()) {
|
if (inst.GetEvolveProgression() < 100 || inst.GetEvolveLvl() == inst.GetMaxEvolveLvl()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-17
@@ -807,22 +807,42 @@ void Client::CompleteConnect()
|
|||||||
m_last_position_before_bulk_update = GetPosition();
|
m_last_position_before_bulk_update = GetPosition();
|
||||||
|
|
||||||
/* This sub event is for if a player logs in for the first time since entering world. */
|
/* This sub event is for if a player logs in for the first time since entering world. */
|
||||||
if (firstlogon == 1) {
|
if (ingame) {
|
||||||
|
auto e = CharacterDataRepository::FindOne(
|
||||||
|
database,
|
||||||
|
CharacterID()
|
||||||
|
);
|
||||||
|
|
||||||
|
bool is_first_login = e.first_login == 0;
|
||||||
|
|
||||||
RecordPlayerEventLog(PlayerEvent::WENT_ONLINE, PlayerEvent::EmptyEvent{});
|
RecordPlayerEventLog(PlayerEvent::WENT_ONLINE, PlayerEvent::EmptyEvent{});
|
||||||
|
|
||||||
if (parse->PlayerHasQuestSub(EVENT_CONNECT)) {
|
if (parse->PlayerHasQuestSub(EVENT_CONNECT)) {
|
||||||
parse->EventPlayer(EVENT_CONNECT, this, "", 0);
|
const std::string& export_string = fmt::format(
|
||||||
|
"{} {} {}",
|
||||||
|
e.last_login,
|
||||||
|
time(nullptr) - e.last_login,
|
||||||
|
is_first_login ? 1 : 0
|
||||||
|
);
|
||||||
|
parse->EventPlayer(EVENT_CONNECT, this, export_string, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (is_first_login) {
|
||||||
* Update last login since this doesn't get updated until a late save later so we can update online status
|
e.first_login = time(nullptr);
|
||||||
*/
|
TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}'", CharacterID()));
|
||||||
database.QueryDatabase(
|
BuyerRepository::DeleteBuyer(database, CharacterID());
|
||||||
StringFormat(
|
LogTradingDetail(
|
||||||
"UPDATE `character_data` SET `last_login` = UNIX_TIMESTAMP() WHERE id = %u",
|
"Removed trader abd buyer entries for Character ID {} on first logon to ensure table consistency.",
|
||||||
CharacterID()
|
CharacterID()
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.last_login = time(nullptr);
|
||||||
|
|
||||||
|
const int updated = CharacterDataRepository::UpdateOne(database, e);
|
||||||
|
if (!updated) {
|
||||||
|
LogError("Failed to update login time for character_id [{}]", CharacterID());
|
||||||
|
}
|
||||||
|
|
||||||
if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) {
|
if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) {
|
||||||
InvokeChangePetName(false);
|
InvokeChangePetName(false);
|
||||||
@@ -871,7 +891,7 @@ void Client::CompleteConnect()
|
|||||||
entity_list.SendFindableNPCList(this);
|
entity_list.SendFindableNPCList(this);
|
||||||
|
|
||||||
if (IsInAGuild()) {
|
if (IsInAGuild()) {
|
||||||
if (firstlogon == 1) {
|
if (ingame) {
|
||||||
guild_mgr.UpdateDbMemberOnline(CharacterID(), true);
|
guild_mgr.UpdateDbMemberOnline(CharacterID(), true);
|
||||||
SendGuildMembersList();
|
SendGuildMembersList();
|
||||||
}
|
}
|
||||||
@@ -1307,7 +1327,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
|
|||||||
|
|
||||||
/* Load Character Data */
|
/* Load Character Data */
|
||||||
query = fmt::format(
|
query = fmt::format(
|
||||||
"SELECT `lfp`, `lfg`, `xtargets`, `firstlogon`, `guild_id`, `rank`, `exp_enabled`, `tribute_enable`, `extra_haste`, `illusion_block` FROM `character_data` LEFT JOIN `guild_members` ON `id` = `char_id` WHERE `id` = {}",
|
"SELECT `lfp`, `lfg`, `xtargets`, `first_login`, `guild_id`, `rank`, `exp_enabled`, `tribute_enable`, `extra_haste`, `illusion_block`, `ingame` FROM `character_data` LEFT JOIN `guild_members` ON `id` = `char_id` WHERE `id` = {}",
|
||||||
cid
|
cid
|
||||||
);
|
);
|
||||||
auto results = database.QueryDatabase(query);
|
auto results = database.QueryDatabase(query);
|
||||||
@@ -1322,10 +1342,21 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
|
|||||||
SetExtraHaste(Strings::ToInt(row[8]), false);
|
SetExtraHaste(Strings::ToInt(row[8]), false);
|
||||||
SetIllusionBlock(Strings::ToBool(row[9]));
|
SetIllusionBlock(Strings::ToBool(row[9]));
|
||||||
|
|
||||||
if (LFP) { LFP = Strings::ToInt(row[0]); }
|
if (LFP) {
|
||||||
if (LFG) { LFG = Strings::ToInt(row[1]); }
|
LFP = Strings::ToInt(row[0]);
|
||||||
if (row[3])
|
}
|
||||||
firstlogon = Strings::ToInt(row[3]);
|
|
||||||
|
if (LFG) {
|
||||||
|
LFG = Strings::ToInt(row[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row[3]) {
|
||||||
|
first_login = Strings::ToUnsignedInt(row[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row[10]) {
|
||||||
|
ingame = Strings::ToBool(row[10]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RuleB(Character, SharedBankPlat))
|
if (RuleB(Character, SharedBankPlat))
|
||||||
@@ -1350,6 +1381,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
|
|||||||
database.LoadCharacterLeadershipAbilities(cid, &m_pp); /* Load Character Leadership AA's */
|
database.LoadCharacterLeadershipAbilities(cid, &m_pp); /* Load Character Leadership AA's */
|
||||||
database.LoadCharacterTribute(this); /* Load CharacterTribute */
|
database.LoadCharacterTribute(this); /* Load CharacterTribute */
|
||||||
database.LoadCharacterEXPModifier(this); /* Load Character EXP Modifier */
|
database.LoadCharacterEXPModifier(this); /* Load Character EXP Modifier */
|
||||||
|
database.LoadCharacterTitleSets(this); /* Load Character Title Sets */
|
||||||
|
|
||||||
// this pattern is strange
|
// this pattern is strange
|
||||||
// this is remnants of the old way of doing things
|
// this is remnants of the old way of doing things
|
||||||
@@ -1713,7 +1745,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
|
|||||||
|
|
||||||
// Taunt persists when zoning on newer clients, overwrite default.
|
// Taunt persists when zoning on newer clients, overwrite default.
|
||||||
if (m_ClientVersionBit & EQ::versions::maskUFAndLater) {
|
if (m_ClientVersionBit & EQ::versions::maskUFAndLater) {
|
||||||
if (!firstlogon) {
|
if (!ingame) {
|
||||||
pet->SetTaunting(m_petinfo.taunting);
|
pet->SetTaunting(m_petinfo.taunting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15567,7 +15599,9 @@ void Client::Handle_OP_TraderShop(const EQApplicationPacket *app)
|
|||||||
switch (in->Code) {
|
switch (in->Code) {
|
||||||
case ClickTrader: {
|
case ClickTrader: {
|
||||||
LogTrading("Handle_OP_TraderShop case ClickTrader [{}]", in->Code);
|
LogTrading("Handle_OP_TraderShop case ClickTrader [{}]", in->Code);
|
||||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_TraderShop, sizeof(TraderClick_Struct));
|
auto outapp =
|
||||||
|
std::make_unique<EQApplicationPacket>(OP_TraderShop, static_cast<uint32>(sizeof(TraderClick_Struct))
|
||||||
|
);
|
||||||
auto data = (TraderClick_Struct *) outapp->pBuffer;
|
auto data = (TraderClick_Struct *) outapp->pBuffer;
|
||||||
auto trader_client = entity_list.GetClientByID(in->TraderID);
|
auto trader_client = entity_list.GetClientByID(in->TraderID);
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
||||||
@@ -725,7 +727,7 @@ void Client::OnDisconnect(bool hard_disconnect) {
|
|||||||
o->trade->Reset();
|
o->trade->Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
database.SetFirstLogon(CharacterID(), 0); //We change firstlogon status regardless of if a player logs out to zone or not, because we only want to trigger it on their first login from world.
|
database.SetIngame(CharacterID(), 0); //We change ingame status regardless of if a player logs out to zone or not, because we only want to trigger it on their first login from world.
|
||||||
|
|
||||||
/* Remove from all proximities */
|
/* Remove from all proximities */
|
||||||
ClearAllProximities();
|
ClearAllProximities();
|
||||||
|
|||||||
@@ -246,6 +246,7 @@ int command_init(void)
|
|||||||
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", "[instance|zone] [Instance ID|Zone ID|Zone Short Name] - Shut down a zone server by Instance ID, Zone ID, or Zone Short Name", AccountStatus::GMLeadAdmin, command_zoneshutdown) ||
|
command_add("zoneshutdown", "[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("zonevariable", "[clear|delete|set|view] - Modify zone variables for your current zone", AccountStatus::GMAdmin, command_zonevariable) ||
|
||||||
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();
|
||||||
@@ -928,6 +929,7 @@ void command_bot(Client *c, const Seperator *sep)
|
|||||||
#include "gm_commands/zone.cpp"
|
#include "gm_commands/zone.cpp"
|
||||||
#include "gm_commands/zonebootup.cpp"
|
#include "gm_commands/zonebootup.cpp"
|
||||||
#include "gm_commands/zoneshutdown.cpp"
|
#include "gm_commands/zoneshutdown.cpp"
|
||||||
|
#include "gm_commands/zonevariable.cpp"
|
||||||
#include "gm_commands/zone_instance.cpp"
|
#include "gm_commands/zone_instance.cpp"
|
||||||
#include "gm_commands/zone_shard.cpp"
|
#include "gm_commands/zone_shard.cpp"
|
||||||
#include "gm_commands/zsave.cpp"
|
#include "gm_commands/zsave.cpp"
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ 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_zonevariable(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"
|
||||||
|
|||||||
@@ -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});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1125,8 +1125,8 @@ void EntityList::AESpell(
|
|||||||
RuleI(Range, MobCloseScanDistance),
|
RuleI(Range, MobCloseScanDistance),
|
||||||
distance
|
distance
|
||||||
);
|
);
|
||||||
|
auto list = caster_mob->GetCloseMobList(distance);
|
||||||
for (auto& it: caster_mob->GetCloseMobList(distance)) {
|
for (auto& it: list) {
|
||||||
current_mob = it.second;
|
current_mob = it.second;
|
||||||
if (!current_mob) {
|
if (!current_mob) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -2543,6 +2543,14 @@ void PerlembParser::ExportEventVariables(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case EVENT_CONNECT: {
|
||||||
|
Seperator sep(data);
|
||||||
|
ExportVar(package_name.c_str(), "last_login", sep.arg[0]);
|
||||||
|
ExportVar(package_name.c_str(), "seconds_since_last_login", sep.arg[1]);
|
||||||
|
ExportVar(package_name.c_str(), "is_first_login", sep.arg[2]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -505,6 +505,9 @@ void EntityList::MobProcess()
|
|||||||
zone->GetSecondsBeforeIdle(),
|
zone->GetSecondsBeforeIdle(),
|
||||||
zone->GetSecondsBeforeIdle() != 1 ? "s" : ""
|
zone->GetSecondsBeforeIdle() != 1 ? "s" : ""
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CheckToClearTraderAndBuyerTables();
|
||||||
|
|
||||||
mob_settle_timer->Disable();
|
mob_settle_timer->Disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3159,6 +3162,13 @@ void EntityList::Depop(bool StartSpawnTimer)
|
|||||||
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);
|
||||||
}
|
}
|
||||||
@@ -6002,3 +6012,22 @@ void EntityList::RestoreCorpse(NPC *npc, uint32_t decay_time)
|
|||||||
c->SetDecayTimer(decay_time);
|
c->SetDecayTimer(decay_time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityList::CheckToClearTraderAndBuyerTables()
|
||||||
|
{
|
||||||
|
if (zone->GetZoneID() == Zones::BAZAAR) {
|
||||||
|
TraderRepository::DeleteWhere(
|
||||||
|
database,
|
||||||
|
fmt::format(
|
||||||
|
"`char_zone_id` = {} AND `char_zone_instance_id` = {}", zone->GetZoneID(), zone->GetInstanceID()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
BuyerRepository::DeleteBuyers(database, zone->GetZoneID(), zone->GetInstanceID());
|
||||||
|
|
||||||
|
LogTradingDetail(
|
||||||
|
"Removed trader and buyer entries for Zone ID [{}] and Instance ID [{}]",
|
||||||
|
zone->GetZoneID(),
|
||||||
|
zone->GetInstanceID()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -581,6 +581,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);
|
void RestoreCorpse(NPC* npc, uint32_t decay_time);
|
||||||
|
void CheckToClearTraderAndBuyerTables();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class Zone;
|
friend class Zone;
|
||||||
|
|||||||
@@ -1693,6 +1693,24 @@ void command_npcedit(Client *c, const Seperator *sep)
|
|||||||
c->Message(Chat::White, "Usage: #npcedit set_grid [Grid ID] - Sets an NPC's Grid ID");
|
c->Message(Chat::White, "Usage: #npcedit set_grid [Grid ID] - Sets an NPC's Grid ID");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else if (!strcasecmp(sep->arg[1], "npc_tint_id")) {
|
||||||
|
if (sep->IsNumber(2)) {
|
||||||
|
const uint32 npc_tint_id = (Strings::ToUnsignedInt(sep->arg[2]));
|
||||||
|
|
||||||
|
n.npc_tint_id = npc_tint_id;
|
||||||
|
|
||||||
|
d = fmt::format(
|
||||||
|
"Set NPCTintID {} for {}",
|
||||||
|
npc_tint_id,
|
||||||
|
npc_id_string
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
c->Message(
|
||||||
|
Chat::White,
|
||||||
|
"Usage: #npcedit npc_tint_id [id] - Sets an NPC's NPCTintID [0 - 78 for RoF2]"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
SendNPCEditSubCommands(c);
|
SendNPCEditSubCommands(c);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ extern WorldServer worldserver;
|
|||||||
|
|
||||||
void command_task(Client *c, const Seperator *sep)
|
void command_task(Client *c, const Seperator *sep)
|
||||||
{
|
{
|
||||||
|
if (!RuleB(TaskSystem, EnableTaskSystem)) {
|
||||||
|
c->Message(Chat::White, "This command cannot be used while the Task system is disabled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const int arguments = sep->argnum;
|
const int arguments = sep->argnum;
|
||||||
if (!arguments) {
|
if (!arguments) {
|
||||||
c->Message(Chat::White, "Syntax: #task [subcommand]");
|
c->Message(Chat::White, "Syntax: #task [subcommand]");
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
#include "../client.h"
|
||||||
|
|
||||||
|
void command_zonevariable(Client *c, const Seperator *sep)
|
||||||
|
{
|
||||||
|
const uint16 arguments = sep->argnum;
|
||||||
|
|
||||||
|
if (!arguments) {
|
||||||
|
c->Message(Chat::White, "Usage: #zonevariable clear - Clear all zone variables");
|
||||||
|
c->Message(Chat::White, "Usage: #zonevariable delete [Variable Name] - Delete a zone variable");
|
||||||
|
c->Message(Chat::White, "Usage: #zonevariable set [Variable Name] [Variable Value] - Set a zone variable");
|
||||||
|
c->Message(Chat::White, "Usage: #zonevariable view [Variable Name] - View a zone variable");
|
||||||
|
c->Message(Chat::White, "Note: You can have spaces in variable names and values by wrapping in double quotes like this");
|
||||||
|
c->Message(Chat::White, "Example: #zonevariable set \"Test Variable\" \"Test Value\"");
|
||||||
|
c->Message(Chat::White, "Note: Variable Value is optional and can be set to empty by not providing a value");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* action = arguments >= 1 ? sep->arg[1] : "";
|
||||||
|
const bool is_clear = !strcasecmp(action, "clear");
|
||||||
|
const bool is_delete = !strcasecmp(action, "delete");
|
||||||
|
const bool is_set = !strcasecmp(action, "set");
|
||||||
|
const bool is_view = !strcasecmp(action, "view");
|
||||||
|
|
||||||
|
if (!is_clear && !is_delete && !is_set && !is_view) {
|
||||||
|
c->Message(Chat::White, "Usage: #zonevariable clear - Clear all zone variables");
|
||||||
|
c->Message(Chat::White, "Usage: #zonevariable delete [Variable Name] - Delete a zone variable");
|
||||||
|
c->Message(Chat::White, "Usage: #zonevariable set [Variable Name] [Variable Value] - Set a zone variable");
|
||||||
|
c->Message(Chat::White, "Usage: #zonevariable view [Variable Name] - View a zone variable");
|
||||||
|
c->Message(Chat::White, "Note: You can have spaces in variable names and values by wrapping in double quotes like this");
|
||||||
|
c->Message(Chat::White, "Example: #zonevariable set \"Test Variable\" \"Test Value\"");
|
||||||
|
c->Message(Chat::White, "Note: Variable Value is optional and can be set to empty by not providing a value");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_clear) {
|
||||||
|
const bool cleared = zone->ClearVariables();
|
||||||
|
c->Message(Chat::White, cleared ? "Cleared all zone variables." : "There are no zone variables to clear.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_delete) {
|
||||||
|
const std::string variable_name = arguments >= 2 ? sep->argplus[2] : "";
|
||||||
|
if (variable_name.empty() || !zone->VariableExists(variable_name)) {
|
||||||
|
c->Message(Chat::White, fmt::format("A zone variable named '{}' does not exist.", variable_name).c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
zone->DeleteVariable(variable_name);
|
||||||
|
c->Message(Chat::White, fmt::format("Deleted a zone variable named '{}'.", variable_name).c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_set) {
|
||||||
|
const std::string variable_name = arguments >= 2 ? sep->arg[2] : "";
|
||||||
|
const std::string variable_value = arguments >= 3 ? sep->arg[3] : "";
|
||||||
|
zone->SetVariable(variable_name, variable_value);
|
||||||
|
c->Message(Chat::White, fmt::format("Set a zone variable named '{}' to a value of '{}'.", variable_name, variable_value).c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_view) {
|
||||||
|
const auto& l = zone->GetVariables();
|
||||||
|
const std::string search_criteria = arguments >= 2 ? sep->argplus[2] : "";
|
||||||
|
|
||||||
|
uint32 variable_count = 0;
|
||||||
|
uint32 variable_number = 1;
|
||||||
|
|
||||||
|
for (const auto& e : l) {
|
||||||
|
if (search_criteria.empty() || Strings::Contains(Strings::ToLower(e), Strings::ToLower(search_criteria))) {
|
||||||
|
c->Message(Chat::White, fmt::format(
|
||||||
|
"Variable {} | Name: {} Value: {} | {}",
|
||||||
|
variable_number,
|
||||||
|
e,
|
||||||
|
zone->GetVariable(e),
|
||||||
|
Saylink::Silent(fmt::format("#zonevariable delete {}", e), "Delete")
|
||||||
|
).c_str());
|
||||||
|
|
||||||
|
variable_count++;
|
||||||
|
variable_number++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!variable_count) {
|
||||||
|
c->Message(Chat::White, fmt::format(
|
||||||
|
"There are no zone variables{}.",
|
||||||
|
(!search_criteria.empty() ? fmt::format(" matching '{}'", search_criteria) : "")
|
||||||
|
).c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
c->Message(Chat::White, fmt::format(
|
||||||
|
"There {} {} zone variable{}{}, would you like to {} zone variables?",
|
||||||
|
variable_count != 1 ? "are" : "is",
|
||||||
|
variable_count,
|
||||||
|
variable_count != 1 ? "s" : "",
|
||||||
|
(!search_criteria.empty() ? fmt::format(" matching '{}'", search_criteria) : ""),
|
||||||
|
Saylink::Silent("#zonevariable clear", "clear")
|
||||||
|
).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -406,6 +406,7 @@ void ZoneGuildManager::ProcessWorldPacket(ServerPacket *pack)
|
|||||||
c.second->SendGuildDeletePacket(s->guild_id);
|
c.second->SendGuildDeletePacket(s->guild_id);
|
||||||
c.second->RefreshGuildInfo();
|
c.second->RefreshGuildInfo();
|
||||||
c.second->MessageString(Chat::Guild, GUILD_DISBANDED);
|
c.second->MessageString(Chat::Guild, GUILD_DISBANDED);
|
||||||
|
c.second->SendGuildList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1094,8 +1094,8 @@ int lua_faction_value() {
|
|||||||
return quest_manager.FactionValue();
|
return quest_manager.FactionValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void lua_check_title(uint32 title_set) {
|
bool lua_check_title(uint32 title_set) {
|
||||||
quest_manager.checktitle(title_set);
|
return quest_manager.checktitle(title_set);
|
||||||
}
|
}
|
||||||
|
|
||||||
void lua_enable_title(uint32 title_set) {
|
void lua_enable_title(uint32 title_set) {
|
||||||
|
|||||||
@@ -945,6 +945,12 @@ bool Lua_NPC::IsResumedFromZoneSuspend()
|
|||||||
return self->IsResumedFromZoneSuspend();
|
return self->IsResumedFromZoneSuspend();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Lua_NPC::SetNPCTintIndex(uint32 id)
|
||||||
|
{
|
||||||
|
Lua_Safe_Call_Void();
|
||||||
|
self->SendAppearancePacket(AppearanceType::NPCTintIndex, id);
|
||||||
|
}
|
||||||
|
|
||||||
luabind::scope lua_register_npc() {
|
luabind::scope lua_register_npc() {
|
||||||
return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
|
return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
|
||||||
.def(luabind::constructor<>())
|
.def(luabind::constructor<>())
|
||||||
@@ -1091,6 +1097,7 @@ luabind::scope lua_register_npc() {
|
|||||||
.def("SetLDoNTrapType", (void(Lua_NPC::*)(uint8))&Lua_NPC::SetLDoNTrapType)
|
.def("SetLDoNTrapType", (void(Lua_NPC::*)(uint8))&Lua_NPC::SetLDoNTrapType)
|
||||||
.def("SetNPCAggro", (void(Lua_NPC::*)(bool))&Lua_NPC::SetNPCAggro)
|
.def("SetNPCAggro", (void(Lua_NPC::*)(bool))&Lua_NPC::SetNPCAggro)
|
||||||
.def("SetNPCFactionID", (void(Lua_NPC::*)(int))&Lua_NPC::SetNPCFactionID)
|
.def("SetNPCFactionID", (void(Lua_NPC::*)(int))&Lua_NPC::SetNPCFactionID)
|
||||||
|
.def("SetNPCTintIndex", &Lua_NPC::SetNPCTintIndex)
|
||||||
.def("SetPetSpellID", (void(Lua_NPC::*)(int))&Lua_NPC::SetPetSpellID)
|
.def("SetPetSpellID", (void(Lua_NPC::*)(int))&Lua_NPC::SetPetSpellID)
|
||||||
.def("SetPlatinum", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetPlatinum)
|
.def("SetPlatinum", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetPlatinum)
|
||||||
.def("SetPrimSkill", (void(Lua_NPC::*)(int))&Lua_NPC::SetPrimSkill)
|
.def("SetPrimSkill", (void(Lua_NPC::*)(int))&Lua_NPC::SetPrimSkill)
|
||||||
|
|||||||
@@ -199,6 +199,8 @@ public:
|
|||||||
void ReturnHandinItems(Lua_Client c);
|
void ReturnHandinItems(Lua_Client c);
|
||||||
Lua_Spawn GetSpawn(lua_State* L);
|
Lua_Spawn GetSpawn(lua_State* L);
|
||||||
bool IsResumedFromZoneSuspend();
|
bool IsResumedFromZoneSuspend();
|
||||||
|
void SetNPCTintIndex(uint32 id);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -353,6 +353,7 @@ LuaParser::LuaParser() {
|
|||||||
PlayerArgumentDispatch[EVENT_AA_LOSS] = handle_player_aa_loss;
|
PlayerArgumentDispatch[EVENT_AA_LOSS] = handle_player_aa_loss;
|
||||||
PlayerArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_player_spell_blocked;
|
PlayerArgumentDispatch[EVENT_SPELL_BLOCKED] = handle_player_spell_blocked;
|
||||||
PlayerArgumentDispatch[EVENT_READ_ITEM] = handle_player_read_item;
|
PlayerArgumentDispatch[EVENT_READ_ITEM] = handle_player_read_item;
|
||||||
|
PlayerArgumentDispatch[EVENT_CONNECT] = handle_player_connect;
|
||||||
|
|
||||||
ItemArgumentDispatch[EVENT_ITEM_CLICK] = handle_item_click;
|
ItemArgumentDispatch[EVENT_ITEM_CLICK] = handle_item_click;
|
||||||
ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click;
|
ItemArgumentDispatch[EVENT_ITEM_CLICK_CAST] = handle_item_click;
|
||||||
|
|||||||
@@ -1809,6 +1809,26 @@ void handle_player_read_item(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handle_player_connect(
|
||||||
|
QuestInterface *parse,
|
||||||
|
lua_State* L,
|
||||||
|
Client* client,
|
||||||
|
std::string data,
|
||||||
|
uint32 extra_data,
|
||||||
|
std::vector<std::any> *extra_pointers
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Seperator sep(data.c_str());
|
||||||
|
lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[0]));
|
||||||
|
lua_setfield(L, -2, "last_login");
|
||||||
|
|
||||||
|
lua_pushinteger(L, Strings::ToUnsignedInt(sep.arg[1]));
|
||||||
|
lua_setfield(L, -2, "seconds_since_last_login");
|
||||||
|
|
||||||
|
lua_pushboolean(L, Strings::ToBool(sep.arg[2]));
|
||||||
|
lua_setfield(L, -2, "is_first_login");
|
||||||
|
}
|
||||||
|
|
||||||
// Item
|
// Item
|
||||||
void handle_item_click(
|
void handle_item_click(
|
||||||
QuestInterface *parse,
|
QuestInterface *parse,
|
||||||
|
|||||||
@@ -865,6 +865,15 @@ void handle_player_read_item(
|
|||||||
std::vector<std::any> *extra_pointers
|
std::vector<std::any> *extra_pointers
|
||||||
);
|
);
|
||||||
|
|
||||||
|
void handle_player_connect(
|
||||||
|
QuestInterface *parse,
|
||||||
|
lua_State* L,
|
||||||
|
Client* client,
|
||||||
|
std::string data,
|
||||||
|
uint32 extra_data,
|
||||||
|
std::vector<std::any> *extra_pointers
|
||||||
|
);
|
||||||
|
|
||||||
// Item
|
// Item
|
||||||
void handle_item_click(
|
void handle_item_click(
|
||||||
QuestInterface *parse,
|
QuestInterface *parse,
|
||||||
|
|||||||
+3
-3
@@ -727,10 +727,10 @@ std::string Lua_Zone::GetBucketRemaining(const std::string& bucket_name)
|
|||||||
return self->GetBucketRemaining(bucket_name);
|
return self->GetBucketRemaining(bucket_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Lua_Zone::ClearVariables()
|
bool Lua_Zone::ClearVariables()
|
||||||
{
|
{
|
||||||
Lua_Safe_Call_Void();
|
Lua_Safe_Call_Bool();
|
||||||
self->ClearVariables();
|
return self->ClearVariables();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Lua_Zone::DeleteVariable(const std::string& variable_name)
|
bool Lua_Zone::DeleteVariable(const std::string& variable_name)
|
||||||
|
|||||||
+1
-1
@@ -141,7 +141,7 @@ public:
|
|||||||
void SetInstanceTimeRemaining(uint32 time_remaining);
|
void SetInstanceTimeRemaining(uint32 time_remaining);
|
||||||
void SetIsHotzone(bool is_hotzone);
|
void SetIsHotzone(bool is_hotzone);
|
||||||
void ShowZoneGlobalLoot(Lua_Client c);
|
void ShowZoneGlobalLoot(Lua_Client c);
|
||||||
void ClearVariables();
|
bool ClearVariables();
|
||||||
bool DeleteVariable(const std::string& variable_name);
|
bool DeleteVariable(const std::string& variable_name);
|
||||||
std::string GetVariable(const std::string& variable_name);
|
std::string GetVariable(const std::string& variable_name);
|
||||||
luabind::object GetVariables(lua_State* L);
|
luabind::object GetVariables(lua_State* L);
|
||||||
|
|||||||
@@ -678,6 +678,7 @@ int main(int argc, char **argv)
|
|||||||
safe_delete(Config);
|
safe_delete(Config);
|
||||||
|
|
||||||
if (zone != 0) {
|
if (zone != 0) {
|
||||||
|
zone->SetSaveZoneState(false);
|
||||||
zone->Shutdown(true);
|
zone->Shutdown(true);
|
||||||
}
|
}
|
||||||
//Fix for Linux world server problem.
|
//Fix for Linux world server problem.
|
||||||
|
|||||||
+10
-9
@@ -101,7 +101,8 @@ Mob::Mob(
|
|||||||
bool in_always_aggro,
|
bool in_always_aggro,
|
||||||
int32 in_heroic_strikethrough,
|
int32 in_heroic_strikethrough,
|
||||||
bool in_keeps_sold_items,
|
bool in_keeps_sold_items,
|
||||||
int64 in_hp_regen_per_second
|
int64 in_hp_regen_per_second,
|
||||||
|
uint32 npc_tint_id
|
||||||
) :
|
) :
|
||||||
attack_timer(2000),
|
attack_timer(2000),
|
||||||
attack_dw_timer(2000),
|
attack_dw_timer(2000),
|
||||||
@@ -289,6 +290,7 @@ Mob::Mob(
|
|||||||
always_aggro = in_always_aggro;
|
always_aggro = in_always_aggro;
|
||||||
heroic_strikethrough = in_heroic_strikethrough;
|
heroic_strikethrough = in_heroic_strikethrough;
|
||||||
keeps_sold_items = in_keeps_sold_items;
|
keeps_sold_items = in_keeps_sold_items;
|
||||||
|
m_npc_tint_id = npc_tint_id;
|
||||||
|
|
||||||
InitializeBuffSlots();
|
InitializeBuffSlots();
|
||||||
|
|
||||||
@@ -1302,6 +1304,7 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
|||||||
ns->spawn.deity = deity;
|
ns->spawn.deity = deity;
|
||||||
ns->spawn.animation = 0;
|
ns->spawn.animation = 0;
|
||||||
ns->spawn.findable = findable ? 1 : 0;
|
ns->spawn.findable = findable ? 1 : 0;
|
||||||
|
ns->spawn.npc_tint_id = GetNpcTintId();
|
||||||
|
|
||||||
UpdateActiveLight();
|
UpdateActiveLight();
|
||||||
ns->spawn.light = m_Light.Type[EQ::lightsource::LightActive];
|
ns->spawn.light = m_Light.Type[EQ::lightsource::LightActive];
|
||||||
@@ -1312,6 +1315,7 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
|||||||
ns->spawn.NPC = IsClient() ? 0 : 1;
|
ns->spawn.NPC = IsClient() ? 0 : 1;
|
||||||
ns->spawn.IsMercenary = IsMerc() ? 1 : 0;
|
ns->spawn.IsMercenary = IsMerc() ? 1 : 0;
|
||||||
ns->spawn.targetable_with_hotkey = no_target_hotkey ? 0 : 1; // opposite logic!
|
ns->spawn.targetable_with_hotkey = no_target_hotkey ? 0 : 1; // opposite logic!
|
||||||
|
ns->spawn.untargetable = IsTargetable();
|
||||||
|
|
||||||
ns->spawn.petOwnerId = ownerid;
|
ns->spawn.petOwnerId = ownerid;
|
||||||
|
|
||||||
@@ -1698,13 +1702,6 @@ void Mob::StopMoving()
|
|||||||
|
|
||||||
void Mob::StopMoving(float new_heading)
|
void Mob::StopMoving(float new_heading)
|
||||||
{
|
{
|
||||||
if (IsBot()) {
|
|
||||||
auto bot = CastToBot();
|
|
||||||
|
|
||||||
bot->SetCombatJitterFlag(false);
|
|
||||||
bot->SetCombatOutOfRangeJitterFlag(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
StopNavigation();
|
StopNavigation();
|
||||||
RotateTo(new_heading);
|
RotateTo(new_heading);
|
||||||
|
|
||||||
@@ -4621,8 +4618,12 @@ void Mob::SetZone(uint32 zone_id, uint32 instance_id)
|
|||||||
{
|
{
|
||||||
CastToClient()->GetPP().zone_id = zone_id;
|
CastToClient()->GetPP().zone_id = zone_id;
|
||||||
CastToClient()->GetPP().zoneInstance = instance_id;
|
CastToClient()->GetPP().zoneInstance = instance_id;
|
||||||
|
CastToClient()->SaveCharacterData();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsClient()) {
|
||||||
|
Save(); // bots or other things might be covered here for some reason
|
||||||
}
|
}
|
||||||
Save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mob::Kill() {
|
void Mob::Kill() {
|
||||||
|
|||||||
+5
-2
@@ -192,7 +192,8 @@ public:
|
|||||||
bool in_always_aggros_foes,
|
bool in_always_aggros_foes,
|
||||||
int32 in_heroic_strikethrough,
|
int32 in_heroic_strikethrough,
|
||||||
bool keeps_sold_items,
|
bool keeps_sold_items,
|
||||||
int64 in_hp_regen_per_second = 0
|
int64 in_hp_regen_per_second = 0,
|
||||||
|
uint32 npc_tint_id = 0
|
||||||
);
|
);
|
||||||
virtual ~Mob();
|
virtual ~Mob();
|
||||||
|
|
||||||
@@ -962,7 +963,7 @@ public:
|
|||||||
uint16 GetSympatheticFocusEffect(focusType type, uint16 spell_id);
|
uint16 GetSympatheticFocusEffect(focusType type, uint16 spell_id);
|
||||||
bool TryFadeEffect(int slot);
|
bool TryFadeEffect(int slot);
|
||||||
void DispelMagic(Mob* casterm, uint16 spell_id, int effect_value);
|
void DispelMagic(Mob* casterm, uint16 spell_id, int effect_value);
|
||||||
uint16 GetSpellEffectResistChance(uint16 spell_id);
|
bool TrySpellEffectResist(uint16 spell_id);
|
||||||
int32 GetVulnerability(Mob *caster, uint32 spell_id, uint32 ticsremaining, bool from_buff_tic = false);
|
int32 GetVulnerability(Mob *caster, uint32 spell_id, uint32 ticsremaining, bool from_buff_tic = false);
|
||||||
int64 GetFcDamageAmtIncoming(Mob *caster, int32 spell_id, bool from_buff_tic = false);
|
int64 GetFcDamageAmtIncoming(Mob *caster, int32 spell_id, bool from_buff_tic = false);
|
||||||
int64 GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spell_id); //**** This can be removed when bot healing focus code is updated ****
|
int64 GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spell_id); //**** This can be removed when bot healing focus code is updated ****
|
||||||
@@ -1066,6 +1067,7 @@ public:
|
|||||||
void SendWearChangeAndLighting(int8 last_texture);
|
void SendWearChangeAndLighting(int8 last_texture);
|
||||||
inline uint8 GetActiveLightType() { return m_Light.Type[EQ::lightsource::LightActive]; }
|
inline uint8 GetActiveLightType() { return m_Light.Type[EQ::lightsource::LightActive]; }
|
||||||
bool UpdateActiveLight(); // returns true if change, false if no change
|
bool UpdateActiveLight(); // returns true if change, false if no change
|
||||||
|
uint32 GetNpcTintId() { return m_npc_tint_id; }
|
||||||
|
|
||||||
EQ::LightSourceProfile* GetLightProfile() { return &m_Light; }
|
EQ::LightSourceProfile* GetLightProfile() { return &m_Light; }
|
||||||
|
|
||||||
@@ -1597,6 +1599,7 @@ protected:
|
|||||||
bool rare_spawn;
|
bool rare_spawn;
|
||||||
int32 heroic_strikethrough;
|
int32 heroic_strikethrough;
|
||||||
bool keeps_sold_items;
|
bool keeps_sold_items;
|
||||||
|
uint32 m_npc_tint_id;
|
||||||
|
|
||||||
uint32 m_PlayerState;
|
uint32 m_PlayerState;
|
||||||
uint32 GetPlayerState() { return m_PlayerState; }
|
uint32 GetPlayerState() { return m_PlayerState; }
|
||||||
|
|||||||
@@ -933,16 +933,11 @@ void MobMovementManager::SendCommandToClients(
|
|||||||
|
|
||||||
float MobMovementManager::FixHeading(float in)
|
float MobMovementManager::FixHeading(float in)
|
||||||
{
|
{
|
||||||
auto h = in;
|
int h = static_cast<int>(in) % 512;
|
||||||
while (h > 512.0) {
|
if (h < 0) {
|
||||||
h -= 512.0;
|
h += 512;
|
||||||
}
|
}
|
||||||
|
return static_cast<float>(h);
|
||||||
while (h < 0.0) {
|
|
||||||
h += 512.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return h;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MobMovementManager::DumpStats(Client *client)
|
void MobMovementManager::DumpStats(Client *client)
|
||||||
|
|||||||
+7
-1
@@ -128,7 +128,8 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
|||||||
npc_type_data->always_aggro,
|
npc_type_data->always_aggro,
|
||||||
npc_type_data->heroic_strikethrough,
|
npc_type_data->heroic_strikethrough,
|
||||||
npc_type_data->keeps_sold_items,
|
npc_type_data->keeps_sold_items,
|
||||||
npc_type_data->hp_regen_per_second
|
npc_type_data->hp_regen_per_second,
|
||||||
|
npc_type_data->m_npc_tint_id
|
||||||
),
|
),
|
||||||
attacked_timer(CombatEventTimer_expire),
|
attacked_timer(CombatEventTimer_expire),
|
||||||
swarm_timer(100),
|
swarm_timer(100),
|
||||||
@@ -451,6 +452,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
|||||||
raid_target = npc_type_data->raid_target;
|
raid_target = npc_type_data->raid_target;
|
||||||
ignore_despawn = npc_type_data->ignore_despawn;
|
ignore_despawn = npc_type_data->ignore_despawn;
|
||||||
m_targetable = !npc_type_data->untargetable;
|
m_targetable = !npc_type_data->untargetable;
|
||||||
|
m_npc_tint_id = npc_type_data->m_npc_tint_id;
|
||||||
|
|
||||||
npc_scale_manager->ScaleNPC(this);
|
npc_scale_manager->ScaleNPC(this);
|
||||||
|
|
||||||
@@ -1260,6 +1262,7 @@ uint32 ZoneDatabase::CreateNewNPCCommand(
|
|||||||
e.see_improved_hide = n->SeeImprovedHide();
|
e.see_improved_hide = n->SeeImprovedHide();
|
||||||
e.see_invis = n->SeeInvisible();
|
e.see_invis = n->SeeInvisible();
|
||||||
e.see_invis_undead = n->SeeInvisibleUndead();
|
e.see_invis_undead = n->SeeInvisibleUndead();
|
||||||
|
e.npc_tint_id = n->GetNpcTintId();
|
||||||
|
|
||||||
|
|
||||||
e = NpcTypesRepository::InsertOne(*this, e);
|
e = NpcTypesRepository::InsertOne(*this, e);
|
||||||
@@ -1399,6 +1402,7 @@ uint32 ZoneDatabase::UpdateNPCTypeAppearance(Client* c, NPC* n)
|
|||||||
e.loottable_id = n->GetLoottableID();
|
e.loottable_id = n->GetLoottableID();
|
||||||
e.merchant_id = n->MerchantType;
|
e.merchant_id = n->MerchantType;
|
||||||
e.face = n->GetLuclinFace();
|
e.face = n->GetLuclinFace();
|
||||||
|
e.npc_tint_id = n->GetNpcTintId();
|
||||||
|
|
||||||
const int updated = NpcTypesRepository::UpdateOne(*this, e);
|
const int updated = NpcTypesRepository::UpdateOne(*this, e);
|
||||||
|
|
||||||
@@ -1539,6 +1543,7 @@ uint32 ZoneDatabase::AddNPCTypes(
|
|||||||
e.runspeed = n->GetRunspeed();
|
e.runspeed = n->GetRunspeed();
|
||||||
e.prim_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand);
|
e.prim_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand);
|
||||||
e.sec_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand);
|
e.sec_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand);
|
||||||
|
e.npc_tint_id = n->GetNpcTintId();
|
||||||
|
|
||||||
e = NpcTypesRepository::InsertOne(*this, e);
|
e = NpcTypesRepository::InsertOne(*this, e);
|
||||||
|
|
||||||
@@ -2172,6 +2177,7 @@ void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
|||||||
ns->spawn.light = GetActiveLightType();
|
ns->spawn.light = GetActiveLightType();
|
||||||
ns->spawn.show_name = NPCTypedata->show_name;
|
ns->spawn.show_name = NPCTypedata->show_name;
|
||||||
ns->spawn.trader = false;
|
ns->spawn.trader = false;
|
||||||
|
ns->spawn.npc_tint_id = GetNpcTintId();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NPC::PetOnSpawn(NewSpawn_Struct* ns)
|
void NPC::PetOnSpawn(NewSpawn_Struct* ns)
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ decay_timer(300000)
|
|||||||
decay_timer.Disable();
|
decay_timer.Disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memset(m_display_name, 0, sizeof(m_display_name));
|
||||||
|
|
||||||
respawn_timer.Disable();
|
respawn_timer.Disable();
|
||||||
|
|
||||||
// Set drop_id to zero - it will be set when added to zone with SetID()
|
// Set drop_id to zero - it will be set when added to zone with SetID()
|
||||||
@@ -122,6 +124,8 @@ decay_timer(300000)
|
|||||||
// Set as much struct data as we can
|
// Set as much struct data as we can
|
||||||
memset(&m_data, 0, sizeof(Object_Struct));
|
memset(&m_data, 0, sizeof(Object_Struct));
|
||||||
|
|
||||||
|
memset(m_display_name, 0, sizeof(m_display_name));
|
||||||
|
|
||||||
m_data.heading = heading;
|
m_data.heading = heading;
|
||||||
m_data.z = z;
|
m_data.z = z;
|
||||||
m_data.zone_id = zone->GetZoneID();
|
m_data.zone_id = zone->GetZoneID();
|
||||||
@@ -164,6 +168,8 @@ decay_timer(300000)
|
|||||||
// Set as much struct data as we can
|
// Set as much struct data as we can
|
||||||
memset(&m_data, 0, sizeof(Object_Struct));
|
memset(&m_data, 0, sizeof(Object_Struct));
|
||||||
|
|
||||||
|
memset(m_display_name, 0, sizeof(m_display_name));
|
||||||
|
|
||||||
m_data.heading = client->GetHeading();
|
m_data.heading = client->GetHeading();
|
||||||
m_data.x = client->GetX();
|
m_data.x = client->GetX();
|
||||||
m_data.y = client->GetY();
|
m_data.y = client->GetY();
|
||||||
@@ -236,6 +242,8 @@ decay_timer(decay_time)
|
|||||||
// Set as much struct data as we can
|
// Set as much struct data as we can
|
||||||
memset(&m_data, 0, sizeof(Object_Struct));
|
memset(&m_data, 0, sizeof(Object_Struct));
|
||||||
|
|
||||||
|
memset(m_display_name, 0, sizeof(m_display_name));
|
||||||
|
|
||||||
m_data.heading = heading;
|
m_data.heading = heading;
|
||||||
m_data.x = x;
|
m_data.x = x;
|
||||||
m_data.y = y;
|
m_data.y = y;
|
||||||
@@ -312,6 +320,8 @@ decay_timer(decay_time)
|
|||||||
m_data.z = z;
|
m_data.z = z;
|
||||||
m_data.zone_id = zone->GetZoneID();
|
m_data.zone_id = zone->GetZoneID();
|
||||||
|
|
||||||
|
memset(m_display_name, 0, sizeof(m_display_name));
|
||||||
|
|
||||||
if (!IsFixZEnabled()) {
|
if (!IsFixZEnabled()) {
|
||||||
FixZ();
|
FixZ();
|
||||||
}
|
}
|
||||||
@@ -353,6 +363,8 @@ void Object::SetID(uint16 set_id)
|
|||||||
// Reset state of object back to zero
|
// Reset state of object back to zero
|
||||||
void Object::ResetState()
|
void Object::ResetState()
|
||||||
{
|
{
|
||||||
|
Close();
|
||||||
|
|
||||||
safe_delete(m_inst);
|
safe_delete(m_inst);
|
||||||
|
|
||||||
m_id = 0;
|
m_id = 0;
|
||||||
@@ -440,6 +452,12 @@ void Object::Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto outapp = new EQApplicationPacket(OP_ClearObject, sizeof(ClearObject_Struct));
|
||||||
|
ClearObject_Struct *cos = (ClearObject_Struct *)outapp->pBuffer;
|
||||||
|
cos->Clear = 1;
|
||||||
|
user->QueuePacket(outapp);
|
||||||
|
safe_delete(outapp);
|
||||||
|
|
||||||
user->SetTradeskillObject(nullptr);
|
user->SetTradeskillObject(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -409,6 +409,13 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in)
|
|||||||
parcel_out.aug_slot_6 = augs.at(5);
|
parcel_out.aug_slot_6 = augs.at(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!inst->IsDroppable(true)) {
|
||||||
|
Message(Chat::Yellow, "Unable to send a parcel that is NO-DROP or contains a NO-DROP item.");
|
||||||
|
SendParcelAck();
|
||||||
|
DoParcelCancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto result = CharacterParcelsRepository::InsertOne(database, parcel_out);
|
auto result = CharacterParcelsRepository::InsertOne(database, parcel_out);
|
||||||
if (!result.id) {
|
if (!result.id) {
|
||||||
LogError(
|
LogError(
|
||||||
|
|||||||
@@ -885,6 +885,11 @@ Spawn2* Perl_NPC_GetSpawn(NPC* self)
|
|||||||
return self->GetSpawn();
|
return self->GetSpawn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Perl_NPC_SetNPCTintIndex(NPC* self, uint32 id)
|
||||||
|
{
|
||||||
|
return self->SendAppearancePacket(AppearanceType::NPCTintIndex, id);
|
||||||
|
}
|
||||||
|
|
||||||
void perl_register_npc()
|
void perl_register_npc()
|
||||||
{
|
{
|
||||||
perl::interpreter perl(PERL_GET_THX);
|
perl::interpreter perl(PERL_GET_THX);
|
||||||
@@ -1034,6 +1039,7 @@ void perl_register_npc()
|
|||||||
package.add("SetGold", &Perl_NPC_SetGold);
|
package.add("SetGold", &Perl_NPC_SetGold);
|
||||||
package.add("SetGrid", &Perl_NPC_SetGrid);
|
package.add("SetGrid", &Perl_NPC_SetGrid);
|
||||||
package.add("SetNPCFactionID", &Perl_NPC_SetNPCFactionID);
|
package.add("SetNPCFactionID", &Perl_NPC_SetNPCFactionID);
|
||||||
|
package.add("SetNPCTintIndex", &Perl_NPC_SetNPCTintIndex);
|
||||||
package.add("SetPetSpellID", &Perl_NPC_SetPetSpellID);
|
package.add("SetPetSpellID", &Perl_NPC_SetPetSpellID);
|
||||||
package.add("SetPlatinum", &Perl_NPC_SetPlatinum);
|
package.add("SetPlatinum", &Perl_NPC_SetPlatinum);
|
||||||
package.add("SetPrimSkill", &Perl_NPC_SetPrimSkill);
|
package.add("SetPrimSkill", &Perl_NPC_SetPrimSkill);
|
||||||
|
|||||||
+2
-2
@@ -561,9 +561,9 @@ std::string Perl_Zone_GetBucketRemaining(Zone* self, const std::string bucket_na
|
|||||||
return self->GetBucketRemaining(bucket_name);
|
return self->GetBucketRemaining(bucket_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Perl_Zone_ClearVariables(Zone* self)
|
bool Perl_Zone_ClearVariables(Zone* self)
|
||||||
{
|
{
|
||||||
self->ClearVariables();
|
return self->ClearVariables();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Perl_Zone_DeleteVariable(Zone* self, const std::string variable_name)
|
bool Perl_Zone_DeleteVariable(Zone* self, const std::string variable_name)
|
||||||
|
|||||||
+2
-2
@@ -699,7 +699,7 @@ void QuestManager::stoptimer(const std::string& timer_name, Mob* m)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto e = QTimerList.begin(); e != QTimerList.end(); ++e) {
|
for (auto e = QTimerList.begin(); e != QTimerList.end(); ++e) {
|
||||||
if (e->mob && e->mob == m) {
|
if (e->mob && e->mob == m && e->name == timer_name) {
|
||||||
parse->EventMob(EVENT_TIMER_STOP, m, nullptr, [&]() { return timer_name; });
|
parse->EventMob(EVENT_TIMER_STOP, m, nullptr, [&]() { return timer_name; });
|
||||||
|
|
||||||
QTimerList.erase(e);
|
QTimerList.erase(e);
|
||||||
@@ -2786,7 +2786,7 @@ bool QuestManager::createBot(const char *name, const char *lastname, uint8 level
|
|||||||
|
|
||||||
std::string test_name = name;
|
std::string test_name = name;
|
||||||
bool available_flag = false;
|
bool available_flag = false;
|
||||||
if (!database.botdb.QueryNameAvailablity(test_name, available_flag)) {
|
if (!database.botdb.QueryNameAvailability(test_name, available_flag)) {
|
||||||
initiator->Message(
|
initiator->Message(
|
||||||
Chat::White,
|
Chat::White,
|
||||||
fmt::format(
|
fmt::format(
|
||||||
|
|||||||
+7
-1
@@ -277,7 +277,13 @@ bool Spawn2::Process() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NPC *npc = new NPC(tmp, this, glm::vec4(x, y, z, heading), GravityBehavior::Water);
|
// zone state restore
|
||||||
|
if (m_stored_location != glm::vec4(0, 0, -1000, 0)) {
|
||||||
|
loc = m_stored_location;
|
||||||
|
m_stored_location = glm::vec4(0, 0, -1000, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
NPC *npc = new NPC(tmp, this, loc, GravityBehavior::Water);
|
||||||
|
|
||||||
npcthis = npc;
|
npcthis = npc;
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ public:
|
|||||||
inline void SetResumedFromZoneSuspend(bool resumed) { m_resumed_from_zone_suspend = resumed; }
|
inline void SetResumedFromZoneSuspend(bool resumed) { m_resumed_from_zone_suspend = resumed; }
|
||||||
inline void SetEntityVariables(std::map<std::string, std::string> vars) { m_entity_variables = vars; }
|
inline void SetEntityVariables(std::map<std::string, std::string> vars) { m_entity_variables = vars; }
|
||||||
inline void SetResumedNPCID(uint32 npc_id) { m_resumed_npc_id = npc_id; }
|
inline void SetResumedNPCID(uint32 npc_id) { m_resumed_npc_id = npc_id; }
|
||||||
|
inline void SetStoredLocation(const glm::vec4& loc) { m_stored_location = loc; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class Zone;
|
friend class Zone;
|
||||||
@@ -108,6 +109,7 @@ private:
|
|||||||
bool m_resumed_from_zone_suspend = false;
|
bool m_resumed_from_zone_suspend = false;
|
||||||
uint32 m_resumed_npc_id = 0;
|
uint32 m_resumed_npc_id = 0;
|
||||||
std::map<std::string, std::string> m_entity_variables = {};
|
std::map<std::string, std::string> m_entity_variables = {};
|
||||||
|
glm::vec4 m_stored_location = {0, 0, -1000, 0}; // use -1000 to indicate unset/zero-state
|
||||||
};
|
};
|
||||||
|
|
||||||
class SpawnCondition {
|
class SpawnCondition {
|
||||||
|
|||||||
+27
-14
@@ -7465,44 +7465,57 @@ void Mob::DispelMagic(Mob* caster, uint16 spell_id, int effect_value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16 Mob::GetSpellEffectResistChance(uint16 spell_id)
|
bool Mob::TrySpellEffectResist(uint16 spell_id)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
SEResist variable
|
||||||
|
0 = spell effect id to be check if can resist
|
||||||
|
1 = percent chance of resistance
|
||||||
|
*/
|
||||||
|
|
||||||
if(!IsValidSpell(spell_id))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1])
|
if (!IsValidSpell(spell_id)) {
|
||||||
return 0;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
uint16 resist_chance = 0;
|
if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int resist_chance = 0;
|
||||||
|
|
||||||
for(int i = 0; i < EFFECT_COUNT; ++i)
|
for(int i = 0; i < EFFECT_COUNT; ++i)
|
||||||
{
|
{
|
||||||
bool found = false;
|
if (spells[spell_id].effect_id[i] == SE_Blank) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for(int d = 0; d < MAX_RESISTABLE_EFFECTS*2; d+=2)
|
for(int d = 0; d < MAX_RESISTABLE_EFFECTS*2; d+=2)
|
||||||
{
|
{
|
||||||
|
resist_chance = 0;
|
||||||
if (spells[spell_id].effect_id[i] == aabonuses.SEResist[d]){
|
if (spells[spell_id].effect_id[i] == aabonuses.SEResist[d]){
|
||||||
resist_chance += aabonuses.SEResist[d+1];
|
resist_chance += aabonuses.SEResist[d+1];
|
||||||
found = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spells[spell_id].effect_id[i] == itembonuses.SEResist[d]){
|
if (spells[spell_id].effect_id[i] == itembonuses.SEResist[d]){
|
||||||
resist_chance += itembonuses.SEResist[d+1];
|
resist_chance += itembonuses.SEResist[d+1];
|
||||||
found = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (spells[spell_id].effect_id[i] == spellbonuses.SEResist[d]){
|
if (spells[spell_id].effect_id[i] == spellbonuses.SEResist[d]){
|
||||||
resist_chance += spellbonuses.SEResist[d+1];
|
resist_chance += spellbonuses.SEResist[d+1];
|
||||||
found = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (found)
|
if (resist_chance) {
|
||||||
continue;
|
if (zone->random.Roll(resist_chance)) {
|
||||||
|
LogSpells("Resisted spell from Spell Effect Resistance, had [{}] chance to resist spell effect id [{}]", resist_chance, spells[spell_id].effect_id[i]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resist_chance;
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mob::TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier){
|
bool Mob::TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier){
|
||||||
|
|||||||
+7
-12
@@ -5359,18 +5359,19 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
|
|||||||
|
|
||||||
if (!CharmTick) {
|
if (!CharmTick) {
|
||||||
//Check for Spell Effect specific resistance chances (ie AA Mental Fortitude)
|
//Check for Spell Effect specific resistance chances (ie AA Mental Fortitude)
|
||||||
int se_resist_bonuses = GetSpellEffectResistChance(spell_id);
|
if (resist_type != RESIST_NONE && TrySpellEffectResist(spell_id)) {
|
||||||
if (se_resist_bonuses && zone->random.Roll(se_resist_bonuses)) {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for Chance to Resist Spell bonuses (ie Sanctification Discipline)
|
// Check for Chance to Resist Spell bonuses (ie Sanctification Discipline)
|
||||||
|
if (!spells[spell_id].no_resist && resist_type != RESIST_NONE) {
|
||||||
int resist_bonuses = CalcResistChanceBonus();
|
int resist_bonuses = CalcResistChanceBonus();
|
||||||
if (resist_bonuses && zone->random.Roll(resist_bonuses) && !IsResurrectionSicknessSpell(spell_id)) {
|
if (resist_bonuses && zone->random.Roll(resist_bonuses) && !IsResurrectionSicknessSpell(spell_id)) {
|
||||||
LogSpells("Resisted spell in sanctification, had [{}] chance to resist", resist_bonuses);
|
LogSpells("Resisted spell in sanctification, had [{}] chance to resist", resist_bonuses);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Special case. If the caster has the Unholy Aura Discipline activated and the spell is HT,
|
// Special case. If the caster has the Unholy Aura Discipline activated and the spell is HT,
|
||||||
// or improved HT then the resist type is disease.
|
// or improved HT then the resist type is disease.
|
||||||
@@ -5667,18 +5668,12 @@ int16 Mob::CalcResistChanceBonus()
|
|||||||
|
|
||||||
int16 Mob::CalcFearResistChance()
|
int16 Mob::CalcFearResistChance()
|
||||||
{
|
{
|
||||||
int resistchance = spellbonuses.ResistFearChance + itembonuses.ResistFearChance;
|
int resist_chance = spellbonuses.ResistFearChance + itembonuses.ResistFearChance + aabonuses.ResistFearChance;
|
||||||
if (IsOfClientBot()) {
|
if (spellbonuses.Fearless || itembonuses.Fearless || aabonuses.Fearless) {
|
||||||
resistchance += aabonuses.ResistFearChance;
|
resist_chance = 100;
|
||||||
if (aabonuses.Fearless == true) {
|
|
||||||
resistchance = 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (spellbonuses.Fearless == true || itembonuses.Fearless == true) {
|
|
||||||
resistchance = 100;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resistchance;
|
return resist_chance;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Mob::GetAOERange(uint16 spell_id)
|
float Mob::GetAOERange(uint16 spell_id)
|
||||||
|
|||||||
+22
-10
@@ -308,7 +308,7 @@ void Client::SetTitleSuffix(std::string suffix)
|
|||||||
safe_delete(outapp);
|
safe_delete(outapp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::EnableTitle(int title_set)
|
void Client::EnableTitle(int title_set, bool insert)
|
||||||
{
|
{
|
||||||
if (CheckTitle(title_set)) {
|
if (CheckTitle(title_set)) {
|
||||||
return;
|
return;
|
||||||
@@ -319,22 +319,26 @@ void Client::EnableTitle(int title_set)
|
|||||||
e.char_id = CharacterID();
|
e.char_id = CharacterID();
|
||||||
e.title_set = title_set;
|
e.title_set = title_set;
|
||||||
|
|
||||||
if (!PlayerTitlesetsRepository::InsertOne(database, e).id) {
|
if (insert) {
|
||||||
|
e = PlayerTitlesetsRepository::InsertOne(database, e);
|
||||||
|
if (!e.id) {
|
||||||
LogError("Error in EnableTitle query for titleset [{}] and charid [{}]", title_set, CharacterID());
|
LogError("Error in EnableTitle query for titleset [{}] and charid [{}]", title_set, CharacterID());
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_player_title_sets.emplace_back(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Client::CheckTitle(int title_set)
|
bool Client::CheckTitle(int title_set)
|
||||||
{
|
{
|
||||||
return !PlayerTitlesetsRepository::GetWhere(
|
for (const auto& e : m_player_title_sets) {
|
||||||
database,
|
if (e.title_set == title_set) {
|
||||||
fmt::format(
|
return true;
|
||||||
"`char_id` = {} AND `title_set` = {}",
|
}
|
||||||
CharacterID(),
|
}
|
||||||
title_set
|
|
||||||
)
|
return false;
|
||||||
).empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::RemoveTitle(int title_set)
|
void Client::RemoveTitle(int title_set)
|
||||||
@@ -357,6 +361,14 @@ void Client::RemoveTitle(int title_set)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto& titles = m_player_title_sets;
|
||||||
|
for (auto e = titles.begin(); e != titles.end(); e++) {
|
||||||
|
if (e->title_set == title_set) {
|
||||||
|
titles.erase(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PlayerTitlesetsRepository::DeleteWhere(
|
PlayerTitlesetsRepository::DeleteWhere(
|
||||||
database,
|
database,
|
||||||
fmt::format(
|
fmt::format(
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include "../common/global_define.h"
|
#include "../common/global_define.h"
|
||||||
#include "../common/events/player_event_logs.h"
|
#include "../common/events/player_event_logs.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
@@ -1234,15 +1235,18 @@ void Client::CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float
|
|||||||
return; //not allowed to go higher.
|
return; //not allowed to go higher.
|
||||||
uint16 maxskill = MaxSkill(tradeskill);
|
uint16 maxskill = MaxSkill(tradeskill);
|
||||||
|
|
||||||
|
float min_skill_up_chance = RuleR(Character, TradeskillUpMinChance);
|
||||||
|
min_skill_up_chance = std::max(min_skill_up_chance, 2.5f);
|
||||||
|
|
||||||
float chance_stage2 = 0;
|
float chance_stage2 = 0;
|
||||||
|
|
||||||
//A successfull combine doubles the stage1 chance for an skillup
|
//A successfull combine doubles the stage1 chance for an skillup
|
||||||
//Some tradeskill are harder than others. See above for more.
|
//Some tradeskill are harder than others. See above for more.
|
||||||
float chance_stage1 = (bonusstat - stat_modifier) / (skillup_modifier * success_modifier);
|
float chance_stage1 = (bonusstat - stat_modifier) / (skillup_modifier * success_modifier);
|
||||||
|
chance_stage1 = std::max(min_skill_up_chance, chance_stage1);
|
||||||
|
|
||||||
//In stage2 the only thing that matters is your current unmodified skill.
|
//In stage2 the only thing that matters is your current unmodified skill
|
||||||
//If you want to customize here you probbably need to implement your own
|
//and the Character:TradeskillUpMinChance rule.
|
||||||
//formula instead of tweaking the below one.
|
|
||||||
if (chance_stage1 > zone->random.Real(0, 99)) {
|
if (chance_stage1 > zone->random.Real(0, 99)) {
|
||||||
if (current_raw_skill < 15) {
|
if (current_raw_skill < 15) {
|
||||||
//Always succeed
|
//Always succeed
|
||||||
@@ -1254,6 +1258,7 @@ void Client::CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float
|
|||||||
//At skill 175, your chance of success falls linearly from 12.5% to 2.5% at skill 300.
|
//At skill 175, your chance of success falls linearly from 12.5% to 2.5% at skill 300.
|
||||||
chance_stage2 = 12.5 - (.08 * (current_raw_skill - 175));
|
chance_stage2 = 12.5 - (.08 * (current_raw_skill - 175));
|
||||||
}
|
}
|
||||||
|
chance_stage2 = std::max(min_skill_up_chance, chance_stage2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chance_stage2 > zone->random.Real(0, 99)) {
|
if (chance_stage2 > zone->random.Real(0, 99)) {
|
||||||
|
|||||||
+36
-22
@@ -1351,10 +1351,10 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto in = (TraderBuy_Struct *) app->pBuffer;
|
auto outapp = std::make_unique<EQApplicationPacket>(OP_Trader, static_cast<uint32>(sizeof(TraderBuy_Struct)));
|
||||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Trader, sizeof(TraderBuy_Struct));
|
|
||||||
auto outtbs = (TraderBuy_Struct *) outapp->pBuffer;
|
auto outtbs = (TraderBuy_Struct *) outapp->pBuffer;
|
||||||
outtbs->item_id = tbs->item_id;
|
outtbs->item_id = tbs->item_id;
|
||||||
|
|
||||||
const EQ::ItemInstance *buy_item = nullptr;
|
const EQ::ItemInstance *buy_item = nullptr;
|
||||||
uint32 item_id = 0;
|
uint32 item_id = 0;
|
||||||
|
|
||||||
@@ -1558,14 +1558,14 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic
|
|||||||
void Client::SendBazaarWelcome()
|
void Client::SendBazaarWelcome()
|
||||||
{
|
{
|
||||||
const auto results = TraderRepository::GetWelcomeData(database);
|
const auto results = TraderRepository::GetWelcomeData(database);
|
||||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_BazaarSearch, sizeof(BazaarWelcome_Struct));
|
EQApplicationPacket outapp(OP_BazaarSearch, static_cast<uint32>(sizeof(BazaarWelcome_Struct)));
|
||||||
auto data = (BazaarWelcome_Struct *) outapp->pBuffer;
|
auto data = (BazaarWelcome_Struct *) outapp.pBuffer;
|
||||||
|
|
||||||
data->action = BazaarWelcome;
|
data->action = BazaarWelcome;
|
||||||
data->traders = results.count_of_traders;
|
data->traders = results.count_of_traders;
|
||||||
data->items = results.count_of_items;
|
data->items = results.count_of_items;
|
||||||
|
|
||||||
QueuePacket(outapp.get());
|
QueuePacket(&outapp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::SendBarterWelcome()
|
void Client::SendBarterWelcome()
|
||||||
@@ -1798,7 +1798,10 @@ void Client::SendBuyerResults(BarterSearchRequest_Struct& bsr)
|
|||||||
|
|
||||||
{ ar(results); }
|
{ ar(results); }
|
||||||
|
|
||||||
auto packet = std::make_unique<EQApplicationPacket>(OP_BuyerItems, ss.str().length() + sizeof(BuyerGeneric_Struct));
|
auto packet = std::make_unique<EQApplicationPacket>(
|
||||||
|
OP_BuyerItems,
|
||||||
|
static_cast<uint32>(ss.str().length()) + static_cast<uint32>(sizeof(BuyerGeneric_Struct))
|
||||||
|
);
|
||||||
auto emu = (BuyerGeneric_Struct *) packet->pBuffer;
|
auto emu = (BuyerGeneric_Struct *) packet->pBuffer;
|
||||||
|
|
||||||
emu->action = Barter_BuyerSearch;
|
emu->action = Barter_BuyerSearch;
|
||||||
@@ -1851,7 +1854,10 @@ void Client::ShowBuyLines(const EQApplicationPacket *app)
|
|||||||
|
|
||||||
{ ar(l); }
|
{ ar(l); }
|
||||||
|
|
||||||
auto packet = std::make_unique<EQApplicationPacket>(OP_BuyerItems, ss.str().length() + sizeof(BuyerGeneric_Struct));
|
auto packet = std::make_unique<EQApplicationPacket>(
|
||||||
|
OP_BuyerItems,
|
||||||
|
static_cast<uint32>(ss.str().length()) + static_cast<uint32>(sizeof(BuyerGeneric_Struct))
|
||||||
|
);
|
||||||
auto emu = (BuyerGeneric_Struct *) packet->pBuffer;
|
auto emu = (BuyerGeneric_Struct *) packet->pBuffer;
|
||||||
|
|
||||||
emu->action = Barter_BuyerInspectBegin;
|
emu->action = Barter_BuyerInspectBegin;
|
||||||
@@ -2075,7 +2081,7 @@ void Client::SellToBuyer(const EQApplicationPacket *app)
|
|||||||
|
|
||||||
auto server_packet = std::make_unique<ServerPacket>(
|
auto server_packet = std::make_unique<ServerPacket>(
|
||||||
ServerOP_BuyerMessaging,
|
ServerOP_BuyerMessaging,
|
||||||
sizeof(BuyerMessaging_Struct)
|
static_cast<uint32>(sizeof(BuyerMessaging_Struct))
|
||||||
);
|
);
|
||||||
|
|
||||||
auto data = (BuyerMessaging_Struct *) server_packet->pBuffer;
|
auto data = (BuyerMessaging_Struct *) server_packet->pBuffer;
|
||||||
@@ -2123,7 +2129,10 @@ void Client::SendBuyerPacket(Client* Buyer) {
|
|||||||
|
|
||||||
void Client::ToggleBuyerMode(bool status)
|
void Client::ToggleBuyerMode(bool status)
|
||||||
{
|
{
|
||||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Barter, sizeof(BuyerSetAppearance_Struct));
|
auto outapp = std::make_unique<EQApplicationPacket>(
|
||||||
|
OP_Barter,
|
||||||
|
static_cast<uint32>(sizeof(BuyerSetAppearance_Struct))
|
||||||
|
);
|
||||||
auto data = (BuyerSetAppearance_Struct *) outapp->pBuffer;
|
auto data = (BuyerSetAppearance_Struct *) outapp->pBuffer;
|
||||||
|
|
||||||
data->action = Barter_BuyerAppearance;
|
data->action = Barter_BuyerAppearance;
|
||||||
@@ -2319,8 +2328,7 @@ void Client::ModifyBuyLine(const EQApplicationPacket *app)
|
|||||||
|
|
||||||
auto packet = std::make_unique<EQApplicationPacket>(
|
auto packet = std::make_unique<EQApplicationPacket>(
|
||||||
OP_BuyerItems,
|
OP_BuyerItems,
|
||||||
ss_customer.str().length() +
|
static_cast<uint32>(ss_customer.str().length()) + static_cast<uint32>(sizeof(BuyerGeneric_Struct))
|
||||||
sizeof(BuyerGeneric_Struct)
|
|
||||||
);
|
);
|
||||||
auto emu = (BuyerGeneric_Struct *) packet->pBuffer;
|
auto emu = (BuyerGeneric_Struct *) packet->pBuffer;
|
||||||
|
|
||||||
@@ -2739,8 +2747,6 @@ void Client::SendBulkBazaarTraders()
|
|||||||
|
|
||||||
SetTraderCount(results.count);
|
SetTraderCount(results.count);
|
||||||
|
|
||||||
SetTraderCount(results.count);
|
|
||||||
|
|
||||||
auto p_size = 4 + 12 * results.count + results.name_length;
|
auto p_size = 4 + 12 * results.count + results.name_length;
|
||||||
auto buffer = std::make_unique<char[]>(p_size);
|
auto buffer = std::make_unique<char[]>(p_size);
|
||||||
memset(buffer.get(), 0, p_size);
|
memset(buffer.get(), 0, p_size);
|
||||||
@@ -2815,7 +2821,10 @@ void Client::DoBazaarInspect(BazaarInspect_Struct &in)
|
|||||||
|
|
||||||
void Client::SendBazaarDeliveryCosts()
|
void Client::SendBazaarDeliveryCosts()
|
||||||
{
|
{
|
||||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_BazaarSearch, sizeof(BazaarDeliveryCost_Struct));
|
auto outapp = std::make_unique<EQApplicationPacket>(
|
||||||
|
OP_BazaarSearch,
|
||||||
|
static_cast<uint32>(sizeof(BazaarDeliveryCost_Struct))
|
||||||
|
);
|
||||||
auto data = (BazaarDeliveryCost_Struct *) outapp->pBuffer;
|
auto data = (BazaarDeliveryCost_Struct *) outapp->pBuffer;
|
||||||
|
|
||||||
data->action = DeliveryCostUpdate;
|
data->action = DeliveryCostUpdate;
|
||||||
@@ -3076,7 +3085,9 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati
|
|||||||
BazaarAuditTrail(tbs->seller_name, GetName(), buy_item->GetItem()->Name, tbs->quantity, tbs->price, 0);
|
BazaarAuditTrail(tbs->seller_name, GetName(), buy_item->GetItem()->Name, tbs->quantity, tbs->price, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto out_server = std::make_unique<ServerPacket>(ServerOP_BazaarPurchase, sizeof(BazaarPurchaseMessaging_Struct));
|
auto out_server = std::make_unique<ServerPacket>(
|
||||||
|
ServerOP_BazaarPurchase, static_cast<uint32>(sizeof(BazaarPurchaseMessaging_Struct))
|
||||||
|
);
|
||||||
auto out_data = (BazaarPurchaseMessaging_Struct *) out_server->pBuffer;
|
auto out_data = (BazaarPurchaseMessaging_Struct *) out_server->pBuffer;
|
||||||
|
|
||||||
out_data->trader_buy_struct = *tbs;
|
out_data->trader_buy_struct = *tbs;
|
||||||
@@ -3113,7 +3124,7 @@ void Client::SendBuyerGreeting(uint32 buyer_id)
|
|||||||
|
|
||||||
void Client::SendSellerBrowsing(const std::string &browser)
|
void Client::SendSellerBrowsing(const std::string &browser)
|
||||||
{
|
{
|
||||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Barter, sizeof(BuyerBrowsing_Struct));
|
auto outapp = std::make_unique<EQApplicationPacket>(OP_Barter, static_cast<uint32>(sizeof(BuyerBrowsing_Struct)));
|
||||||
auto eq = (BuyerBrowsing_Struct *) outapp->pBuffer;
|
auto eq = (BuyerBrowsing_Struct *) outapp->pBuffer;
|
||||||
|
|
||||||
eq->action = Barter_SellerBrowsing;
|
eq->action = Barter_SellerBrowsing;
|
||||||
@@ -3311,7 +3322,7 @@ void Client::SendWindowUpdatesToSellerAndBuyer(BuyerLineSellItem_Struct &blsi)
|
|||||||
if (blsi.item_quantity - blsi.seller_quantity <= 0) {
|
if (blsi.item_quantity - blsi.seller_quantity <= 0) {
|
||||||
auto outapp = std::make_unique<EQApplicationPacket>(
|
auto outapp = std::make_unique<EQApplicationPacket>(
|
||||||
OP_BuyerItems,
|
OP_BuyerItems,
|
||||||
sizeof(BuyerRemoveItemFromMerchantWindow_Struct)
|
static_cast<uint32>(sizeof(BuyerRemoveItemFromMerchantWindow_Struct))
|
||||||
);
|
);
|
||||||
auto data = (BuyerRemoveItemFromMerchantWindow_Struct *) outapp->pBuffer;
|
auto data = (BuyerRemoveItemFromMerchantWindow_Struct *) outapp->pBuffer;
|
||||||
|
|
||||||
@@ -3401,7 +3412,7 @@ void Client::SendBuyerToBarterWindow(Client *buyer, uint32 action)
|
|||||||
{
|
{
|
||||||
auto server_packet = std::make_unique<ServerPacket>(
|
auto server_packet = std::make_unique<ServerPacket>(
|
||||||
ServerOP_BuyerMessaging,
|
ServerOP_BuyerMessaging,
|
||||||
sizeof(BuyerMessaging_Struct)
|
static_cast<uint32>(sizeof(BuyerMessaging_Struct))
|
||||||
);
|
);
|
||||||
auto data = (BuyerMessaging_Struct *) server_packet->pBuffer;
|
auto data = (BuyerMessaging_Struct *) server_packet->pBuffer;
|
||||||
|
|
||||||
@@ -3422,7 +3433,10 @@ void Client::SendBulkBazaarBuyers()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto outapp = std::make_unique<EQApplicationPacket>(OP_Barter, sizeof(BuyerAddBuyertoBarterWindow_Struct));
|
auto outapp = std::make_unique<EQApplicationPacket>(
|
||||||
|
OP_Barter,
|
||||||
|
static_cast<uint32>(sizeof(BuyerAddBuyertoBarterWindow_Struct))
|
||||||
|
);
|
||||||
auto emu = (BuyerAddBuyertoBarterWindow_Struct *) outapp->pBuffer;
|
auto emu = (BuyerAddBuyertoBarterWindow_Struct *) outapp->pBuffer;
|
||||||
|
|
||||||
for (auto const &b: results) {
|
for (auto const &b: results) {
|
||||||
@@ -3665,11 +3679,11 @@ bool Client::ValidateBuyLineItems(std::map<uint32, BuylineItemDetails_Struct> &i
|
|||||||
|
|
||||||
int64 Client::ValidateBuyLineCost(std::map<uint32, BuylineItemDetails_Struct> &item_map)
|
int64 Client::ValidateBuyLineCost(std::map<uint32, BuylineItemDetails_Struct> &item_map)
|
||||||
{
|
{
|
||||||
int64 proposed_total_cost = std::accumulate(
|
uint64 proposed_total_cost = std::accumulate(
|
||||||
item_map.cbegin(),
|
item_map.cbegin(),
|
||||||
item_map.cend(),
|
item_map.cend(),
|
||||||
0,
|
static_cast<uint64>(0),
|
||||||
[](auto prev_sum, const std::pair<uint32, BuylineItemDetails_Struct> &x) {
|
[](uint64 prev_sum, const std::pair<uint32, BuylineItemDetails_Struct> &x) {
|
||||||
return prev_sum + x.second.item_cost;
|
return prev_sum + x.second.item_cost;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user