Compare commits

...

53 Commits

Author SHA1 Message Date
Alex King f29478c105 [Commands] Add #zonevariable Command (#4882)
* [Commands] Add #zonevariable Command

* Update zonevariable.cpp

* Argument safety

---------

Co-authored-by: Chris Miles <akkadius1@gmail.com>
2025-05-14 20:53:43 -05:00
Chris Miles 888a88f966 [Release] 23.6.0 (#4885) 2025-05-14 20:50:49 -05:00
Alex King 3b617a6652 [Quest API] Add Last Login and First Login Flags to EVENT_CONNECT (#4866)
* [Quest API] Add Last Login and First Login Flags to EVENT_CONNECT

* Push

* Update base_character_data_repository.h

* Update version field

---------

Co-authored-by: Chris Miles <akkadius1@gmail.com>
2025-05-14 20:46:25 -05:00
Alex King c36c336bc7 [Performance] Store Player Title Sets in Client Memory (#4836)
* [Performance] Store Player Title Sets in Zone Memory

* Move to client memory

* Update client_packet.cpp

* Update zonedb.cpp

* Save only when necessary

* Single Insert

* Add optional insert flag

* Update client.h

* Consolidation
2025-05-14 20:46:11 -05:00
zrix-eq 4a9779635d [Feature] Add Character:TradeskillUpMinChance rule (#4867) 2025-05-14 20:27:09 -05:00
Mitch Freeman 20da490bda [Feature] Enable spawn attribute for NPCTintID (#4871)
* Add scripting for NPCTintIndex

* AddNPCTintID

Add NPCTintID to spawn logic

* Update base_npc_types_repository.h

* Correct version.h
2025-05-14 20:26:35 -05:00
Mitch Freeman 1221e88d92 [Fix] Add trader/buyer cleanup actions (#4843)
* Add trader/buyer cleanup actions

Add trader/buyer db cleanup for
- on zone idle
- on client first login
- when world drops a zone connection
- in Client::ProcessMovePC

Cleanup several compiler warnings

* Formatting Updates
2025-05-14 20:24:59 -05:00
nytmyr a2b28b2e16 [Bots] Correct ^pull logic and add checks for Enchanter pets (#4827)
* [Bots] Fix bots getting stuck on ^pull

- Removed initial LoS check from bots and rely only on owner when told to ^pull. This prevents bots from getting stuck in a loop if they don't have LoS when trying to initiate the pull.

* [Bots] Add valid state checks to ^clickitem

- Bots previously weren't checking if they were in a valid state or not before trying to click an item when told (in a death state, held, feared, silenced, in group/raid, etc.)

* [Bots] Add valid state checks to ^clickitem

- Bots previously weren't checking if they were in a valid state or not before trying to click an item when told (in a death state, held, feared, silenced, in group/raid, etc.)

Miscommit

* undo

* Cleanup and Fixes

- Cleanup
- Fixed Animation Empathy checks and consolidates
- Enchanter pets will honor Animation Empathy rules all around
- Pets will properly guard if allowed when pulling
2025-05-14 20:21:25 -05:00
zimp-wow 3d607d352c [Fix] Fix Object Name Init, User Refs, and Client Sync on Close (#4861)
* [Bugfix] Fix uninitialized char* in object.cpp.

* [Bugfix] Clear object user and user tradeskill object on reset.

* [Bugfix] Send clear object packet on Object::Close()
2025-05-14 20:20:57 -05:00
JJ 83cd8119c8 [CLI] ETL Settings Output (#4873)
* Update etl_get_settings.cpp

* Update etl_get_settings.cpp

---------

Co-authored-by: Chris Miles <akkadius1@gmail.com>
2025-05-14 20:19:30 -05:00
nytmyr 4de8fbbd56 [Bots] Fix creation limit, spawn limit, level requirement checks (#4868)
* [Bots] Fix creation limit, spawn limit, level requirement checks

- Previously if buckets were being used to control any of these values and the appropriate rule was set to 0, unset class specific buckets would override the main limit buckets.
- For example, if `Bots:SpawnLimit` is set to `0` and a player has their `bot_spawn_limit` set to `5` but they don't have a class bucket set for the class they're attempting to spawn a Cleric, the unset `bot_spawn_limit_Cleric` would return 0 and prevent Clerics from being spawned.
- This affected spawn limits, creation limits and level requirements to use bots if controlled by buckets.
- `#gm on` is required to be on for those beyond the ruled min status requirements to bypass the limits.

Rewrote checks and tested every scenario of set unset rules/buckets.

* Cleanup, fix bot count

- Fixes QueryBotCount to not account for soft deleted bots (`-deleted-`)
2025-05-14 20:13:50 -05:00
nytmyr 780120036d [Bots] Move all spell_id instances to uint16 (#4876)
* [Bots] Move all spell_id instances to uint16

* Alignment
2025-05-14 19:58:18 -05:00
catapultam-habeo f3697e633c [Fix] Prevent Ranged Attack from being triggered at arbitrary rate (#4879) 2025-05-14 19:55:14 -05:00
hbingram 24f8d88333 [Fix] Fix breaking change to UF patches caused by Big Bags update (#4883)
* Fix breaking change to UF patches caused by Big Bags update

* Update comment

* Removed original line that was commented.
2025-05-14 19:51:48 -05:00
carolus21rex 21bd906a4d [Crash] Fix crash bug with pbae and quest scripts spawning mobs (#4884)
If a PBAE is in progress while a mob pops it can cause a crash. Forcing a copy before the ae has a chance to pop a mob should fix the weirdness
2025-05-14 19:50:12 -05:00
JJ 138612bc88 Only resume NPC if npc_id is ready (#4875) 2025-05-02 16:25:18 -04:00
dependabot[bot] 5919bb4dea Bump golang.org/x/net in /utils/scripts/build/should-release (#4864)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.36.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.36.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.38.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-02 16:24:44 -04:00
Alex King 99d249fefd [Bug Fix] Fix Crash with #task (#4874) 2025-04-30 19:15:53 -04:00
Mitch Freeman c08f286817 AddUntargetableToSpawn (#4872)
Adds the db entry for untargetable to the spawn struct
2025-04-28 17:50:57 -04:00
nytmyr cd003ff0b7 [Cleanup] Fix typo in QueryNameAvailablity (#4869) 2025-04-28 17:49:03 -04:00
nytmyr 1a539f6656 [Hotfix] Fix #copycharacter command (#4860)
This was failing due to a column change in `inventory` from `charid` to `character_id` and would result in no inventory for the new character.
2025-04-28 17:48:30 -04:00
nytmyr 7e7fb7b758 [Bots] Prevent non-taunters from potentially fleeing mob on TargetReflection (#4859)
- Casters could endlessly flee a mob if they had target reflection and were too close.
- Prevents all bots that aren't taunting from adjusting if they're too close and have target reflection. This is safer than limiting it to just casters and ranged bots to prevent situations where melee bots might not be able to outrun the mob to get to their melee range and continually flee.
- WE'LL GET THIS RIGHT EVENTUALLY /sigh
2025-04-28 17:46:12 -04:00
Mitch Freeman 5ae87b40e2 Add some evolvingitem crash checks seen over the past few months. (#4870) 2025-04-28 17:45:15 -04:00
Mitch Freeman 0ec07daebb Fix an edge case for sending a no drop item within a parcel (#4865) 2025-04-28 17:43:31 -04:00
Mitch Freeman 9869da2a0a Resolve guild messaging for incorrect guild tags when creating/deleting guilds. (#4863) 2025-04-28 17:43:04 -04:00
Mitch Freeman 617eb4432b Resolve empty trader lists in the bazaar window. This resolves a typo in the GetDistinctTraders routine. (#4862) 2025-04-28 17:41:45 -04:00
Chris Miles a2b2a6a5cf [Release] 23.5.0 (#4858) 2025-04-10 02:11:20 -05:00
Chris Miles 43a5bff84a [Performance] Network Ring Buffers (#4857)
* [Performance] Network Ring Buffers

* Cursor versus linear scan (wtf GPT)
2025-04-10 02:02:25 -05:00
KayenEQ e983d07228 [Spells] Fear resistance effects edge case fixes and support for SPA 102 as an AA (#4848)
* Clean up for fear related bonus variables

SPA 181 FearResistChance  (% chance to outright resist fear) and SPA 102 Fearless (on live used only on pets for  full fear immunity if presents)

The way were calculating the bonuses was mixing the variables we used for each together in a way that could cause conflicts when combined with negate spell effect.

While doing some live testing was able to confirm SPA102 when cast on pets will terminate the fear on the next tick which is consistent with how we have the mechanic coded.

* Update spells.cpp

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2025-04-09 23:49:55 -05:00
catapultam-habeo 90db12483a [Feature] Add rule to consume command text from any channel (#4839)
* vanilla implementation

* fix typo
2025-04-09 22:45:09 -05:00
nytmyr ff71cfbd5b [Bots] Positioning rewrite (#4856)
* [Bots] Positioning rewrite

- Fixes positioning issues where bots could run back and forth or stay at the target's feet.
- Adds checks to ensure bots don't spam positioning updates and complete their initial positioning.
- Cleans up logic and makes it easier to read.

* Cleanup
2025-04-09 22:39:59 -05:00
Mitch Freeman 08bb9de437 [Fix] Add the bazaar search limit to query (#4829) 2025-04-09 21:44:19 -05:00
Chris Miles 16e341906d [Fix] Zone State Spawn2 Location Restore (#4844)
* [Fix] Zone State Spawn2 Location Restore

* Update zone_save_state.cpp

* Update zone_save_state.cpp

* Update zone_save_state.cpp

* Update zone_save_state.cpp
2025-04-09 21:43:18 -05:00
Mitch Freeman 216b3a039f [Fix] Bazaar Search window not working in a DZ (#4828) 2025-04-09 21:33:48 -05:00
nytmyr b813cf71bb [Bots] Add valid state checks to ^clickitem (#4830)
* [Bots] Add valid state checks to ^clickitem

- Bots previously weren't checking if they were in a valid state or not before trying to click an item when told (in a death state, held, feared, silenced, in group/raid, etc.)

* Add error output on no bots selected.=
2025-04-09 21:33:11 -05:00
nytmyr 48ecd1222f [Bots] Restore old buff overwrite blocking (#4832)
- [#4756](https://github.com/EQEmu/Server/pull/4756) changed the way overwriting worked for buffs. It allowed stronger spells to overwrite lesser spells if they were already on the target.
- However, this is a positive change but has its drawbacks with spells of similar value. It could cause an endless buff loop where they constantly replaced one another.

Overall, the con of endless buffing outweighs the benefit of automatic overwriting lesser spells and the control should be left to only spell lists via `^spells` for bot owners as it was before to determine which buffs bots will cast.
2025-04-09 21:32:57 -05:00
nytmyr e758b407e9 [Bots] Flag all buffs with SE_DamageShield as Damage Shield (#4833)
- Certain resist spells have a DS bonus on them and can be casted as a resist rather than a damage shield. This forces anything with a damage shield as a damage shield buff.
2025-04-09 21:32:38 -05:00
KayenEQ 758dd1875e [Spells] Update to SPA 180 SE_ResistSpellChance to not block unresistable spells. (#4847)
* SPA180 bypassed by field no_resist (field209)

SPA 180 SE_ResistSpellChance provides chance to resist spells outright. This should not be checked to resist a spell if spell has (field209 / no_resist) set to 1.

* Update spells.cpp

confirmed on live unresistable spells are not blocked by sanctification disc or related AA's
2025-04-09 21:31:27 -05:00
KayenEQ 8e2961dda5 [Spells] Update to SPA 378 SE_SpellEffectResistChance (#4845)
* SPA 378 update

Update to SPA 378

SE_SpellEffectResistChance provides chance to resist specific spell effects.

This updates allows support for multiple resistance checks against different effects in same spell.

Example. If a spell has a silence and a blind effect and the target has an AA that gives 10% chance to resist silence and 5% chance to resist blind. It will roll against effect respectively each giving a chance to resist the spell.

* Update spells.cpp

Unresistable spells (resisttype = 0) do not ever resist a spell even with SPA378

Example: Pheonix Charm in Plane of Fire, or Storm Comet in Bastion of Thunder. Parsed both and never got a resist despite having the AA that has effect specific resists which both those spells match.
2025-04-09 21:23:39 -05:00
JJ 5522eda6e4 [Cleanup] Update link for legacy EQEmu loginserver account setup (#4826) 2025-04-09 21:15:22 -05:00
Chris Miles ac1469bac2 [Performance] Pre-Compute CLE Server Lists (#4838)
* [Performance] Pre-Compute CLE Server Lists

* Remove debug
2025-04-09 21:11:52 -05:00
zimp-wow c2989e019a [Bugfix] Prevent depops from blocking new spawns. (#4841) 2025-04-09 21:11:05 -05:00
JJ e16b481ba2 [Cleanup] Remove queryserv dump flag (#4842)
Since queryserv tables are deprecated, there is no longer a need to use this flag
2025-04-09 21:10:46 -05:00
zimp-wow 4fc0ffd173 [Bugfix] Load zone variables before encounter_load. (#4846) 2025-04-09 20:59:19 -05:00
Chris Miles b883888a19 [Performance] Character Save Optimizations (#4851) 2025-04-09 20:56:24 -05:00
Chris Miles 50ad97aa0b [Fix] Databuckets Account Cache Loading (#4855) 2025-04-09 20:56:11 -05:00
zimp-wow dca892e258 [Bugfix] Prevent final shutdown from persisting incomplete state. (#4849)
* [Bugfix] Prevent final shutdown from persisting incomplete state.

* [Bugfix] Change shutdown parameters to member variable.
2025-04-09 20:55:56 -05:00
Alex f9fe4ea2ec [Fix] FixHeading Infinite Loop Fix (#4854)
Co-authored-by: KimLS <KimLS@peqtgc.com>
2025-04-09 20:55:35 -05:00
Chris Miles cc30c72538 [API] World API Optimizations (#4850) 2025-04-09 20:55:13 -05:00
JJ d1fd40cd85 [Database] Fix manifest for helmtexture in horses table (#4852)
* Update database_update_manifest.cpp

* Update database_update_manifest.cpp
2025-04-09 20:53:41 -05:00
Chris Miles f3af458cb3 [Netcode] Fix Stale Client Edge Case (#4853)
* [Netcode] Fix Stale Client Edge Case

* [Netcode] Make sure we always set m_close_time
2025-04-09 20:52:46 -05:00
Akkadius a093d04594 [Hotfix] Remove QS Tables From Export 2025-04-09 20:17:28 -05:00
zimp-wow 115df81400 [Bug Fix] Fix missing timer_name check on Mob::StopTimer (#4840) 2025-04-03 20:40:13 -04:00
108 changed files with 2425 additions and 1299 deletions
+108
View File
@@ -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
+4
View File
@@ -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
View File
@@ -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()) {
+6 -6
View File
@@ -1095,13 +1095,13 @@ 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;
CharacterDataRepository::UpdateOne(*this, e); CharacterDataRepository::UpdateOne(*this, e);
} }
@@ -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
View File
@@ -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);
-1
View File
@@ -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;
+27 -2
View File
@@ -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
}, },
+1 -1
View File
@@ -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"},
+2
View File
@@ -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 {
+7 -3
View File
@@ -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"
}; };
} }
+20 -20
View File
@@ -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__);\
+23
View File
@@ -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();
+2 -2
View File
@@ -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;
}; };
+105 -97
View File
@@ -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(
[](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { &m_socket,
buf->base = new char[suggested_size]; [](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
memset(buf->base, 0, suggested_size); if (suggested_size > 65536) {
buf->len = suggested_size; buf->base = new char[suggested_size];
}, buf->len = suggested_size;
return;
}
static thread_local char temp_buf[65536];
buf->base = temp_buf;
buf->len = 65536;
},
[](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);
delete[] buf->base;
if (buf->len > 65536) {
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,16 +355,16 @@ 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,99 +1343,97 @@ 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; }
case EncodeXOR: break;
if (out.GetInt8(0) == 0) case EncodeXOR:
Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size()); if (out.GetInt8(0) == 0) {
else Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
Encode(out, 1, out.Length() - 1); } else {
break; Encode(out, 1, out.Length() - 1);
default: }
break; break;
default:
break;
} }
} }
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 {
memcpy(data, p.Data(), p.Length());
m_stats.sent_bytes += out.Length(); send_buffers[0] = uv_buf_init(data, p.Length());
m_stats.sent_packets++;
if (m_owner->m_options.simulated_out_packet_loss && m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) {
delete[](char*)send_req->data;
delete send_req;
return;
}
uv_udp_send(send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr*)&send_addr, send_func);
return;
} }
m_stats.bytes_before_encode += p.Length();
uv_udp_send_t *send_req = new uv_udp_send_t;
sockaddr_in send_addr;
uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr);
uv_buf_t send_buffers[1];
char *data = new char[p.Length()];
memcpy(data, p.Data(), p.Length());
send_buffers[0] = uv_buf_init(data, p.Length());
send_req->data = send_buffers[0].base;
m_stats.sent_bytes += p.Length(); m_stats.sent_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)
+1
View File
@@ -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>
+123
View File
@@ -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;
}
};
+1
View File
@@ -171,3 +171,4 @@ namespace EQ
}; };
} }
} }
+3 -3
View File
@@ -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();
}); });
+108 -55
View File
@@ -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) { {
buf->base = new char[suggested_size]; uv_read_start(
buf->len = suggested_size; (uv_stream_t *) m_socket, [](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
}, [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { if (suggested_size > 65536) {
buf->base = new char[suggested_size];
buf->len = suggested_size;
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);
}
else if (nread == UV_EOF) {
connection->Disconnect();
}
else if (nread < 0) {
connection->Disconnect();
}
if (buf->base) { if (buf->len > 65536) {
delete[] buf->base; delete [] buf->base;
} }
} }
else if (nread == UV_EOF) { );
connection->Disconnect();
if (buf->base) {
delete[] buf->base;
}
}
else if (nread < 0) {
connection->Disconnect();
if (buf->base) {
delete[] buf->base;
}
}
});
} }
void EQ::Net::TCPConnection::OnRead(std::function<void(TCPConnection*, const unsigned char*, size_t)> cb) void EQ::Net::TCPConnection::OnRead(std::function<void(TCPConnection*, const unsigned char*, size_t)> cb)
@@ -130,43 +134,92 @@ 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;
baton->connection = this;
baton->buffer = new char[count];
uv_write_t *write_req = new uv_write_t;
memset(write_req, 0, sizeof(uv_write_t));
write_req->data = baton;
uv_buf_t send_buffers[1];
memcpy(baton->buffer, data, count);
send_buffers[0] = uv_buf_init(baton->buffer, count);
uv_write(write_req, (uv_stream_t*)m_socket, send_buffers, 1, [](uv_write_t* req, int status) {
WriteBaton *baton = (WriteBaton*)req->data;
delete[] baton->buffer;
delete req;
if (status < 0) {
baton->connection->Disconnect();
} }
delete baton; TCPWriteReq* write_req = *req_opt;
});
// Fill buffer and set context
memcpy(write_req->buffer.data(), data, count);
write_req->connection = this;
write_req->magic = 0xC0FFEE;
uv_buf_t buf = uv_buf_init(write_req->buffer.data(), static_cast<unsigned int>(count));
int result = uv_write(
&write_req->req,
reinterpret_cast<uv_stream_t*>(m_socket),
&buf,
1,
[](uv_write_t* req, int status) {
auto* full_req = reinterpret_cast<TCPWriteReq*>(req);
if (full_req->magic != 0xC0FFEE) {
std::cerr << "uv_write callback - invalid magic, skipping release\n";
return;
}
tcp_write_pool.release(full_req);
if (status < 0 && full_req->connection) {
std::cerr << "uv_write failed: " << uv_strerror(status) << std::endl;
full_req->connection->Disconnect();
}
}
);
if (result < 0) {
std::cerr << "uv_write() failed immediately: " << uv_strerror(result) << std::endl;
tcp_write_pool.release(write_req);
}
} else {
// Slow path: allocate heap buffer for large write
LogNetTCP("[TCPConnection] Large write of [{}] bytes, using heap buffer", count);
char* heap_buffer = new char[count];
memcpy(heap_buffer, data, count);
uv_write_t* write_req = new uv_write_t;
write_req->data = heap_buffer;
uv_buf_t buf = uv_buf_init(heap_buffer, static_cast<unsigned int>(count));
int result = uv_write(
write_req,
reinterpret_cast<uv_stream_t*>(m_socket),
&buf,
1,
[](uv_write_t* req, int status) {
char* data = static_cast<char*>(req->data);
delete[] data;
delete req;
if (status < 0) {
std::cerr << "uv_write (large) failed: " << uv_strerror(status) << std::endl;
}
}
);
if (result < 0) {
std::cerr << "uv_write() (large) failed immediately: " << uv_strerror(result) << std::endl;
delete[] heap_buffer;
delete write_req;
}
}
} }
std::string EQ::Net::TCPConnection::LocalIP() const std::string EQ::Net::TCPConnection::LocalIP() const
{ {
sockaddr_storage addr; sockaddr_storage addr;
+1
View File
@@ -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>
+125
View File
@@ -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;
}
};
+7 -7
View File
@@ -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;
@@ -4839,13 +4839,13 @@ namespace RoF2
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petOwnerId); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petOwnerId);
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
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // ^ VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // ^
if ((emu->NPC == 0) || (emu->race <= Race::Gnome) || (emu->race == Race::Iksar) || if ((emu->NPC == 0) || (emu->race <= Race::Gnome) || (emu->race == Race::Iksar) ||
(emu->race == Race::VahShir) || (emu->race == Race::Froglok2) || (emu->race == Race::Drakkin) (emu->race == Race::VahShir) || (emu->race == Race::Froglok2) || (emu->race == Race::Drakkin)
+11 -11
View File
@@ -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) + ")");
} }
+51 -14
View File
@@ -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,23 +116,65 @@ 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 false; 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("`buyer_buy_lines_id` IN({})", Strings::Implode(", ", buy_line_ids))
fmt::format(
"`buyer_buy_lines_id` IN({})",
Strings::Implode(", ", buy_line_ids))
); );
} }
return true; 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;
}
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(
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;
}
}; };
#endif //EQEMU_BUYER_REPOSITORY_H #endif //EQEMU_BUYER_REPOSITORY_H
+23 -10
View File
@@ -54,17 +54,30 @@ 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)) {
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name " results = db.QueryDatabase(fmt::format(
"FROM trader AS t " "SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"JOIN character_data AS c ON t.char_id = c.id " "FROM trader AS t "
"WHERE t.char_zone_instance_id = {} " "JOIN character_data AS c ON t.char_id = c.id "
"ORDER BY t.char_zone_instance_id ASC " "WHERE t.char_zone_instance_id = {} "
"LIMIT {}", "ORDER BY t.char_zone_instance_id ASC "
char_zone_instance_id, "LIMIT {}",
max_results) char_zone_instance_id,
); max_results)
);
}
else {
results = db.QueryDatabase(fmt::format(
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"FROM trader AS t "
"JOIN character_data AS c ON t.char_id = c.id "
"ORDER BY t.char_zone_instance_id ASC "
"LIMIT {}",
max_results)
);
}
distinct_traders.reserve(results.RowCount()); distinct_traders.reserve(results.RowCount());
+4 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+2 -2
View File
@@ -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
) )
+4 -4
View File
@@ -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=
-2
View File
@@ -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"
+1 -1
View File
@@ -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;
+145 -102
View File
@@ -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,62 +1630,68 @@ void ClientList::GetClientList(Json::Value &response)
Json::Value row; Json::Value row;
row["account_id"] = cle->AccountID(); row["id"] = cle->GetID();
row["account_name"] = cle->AccountName(); row["name"] = cle->name();
row["admin"] = cle->Admin(); row["level"] = cle->level();
row["id"] = cle->GetID(); row["ip"] = cle->GetIP();
row["ip"] = cle->GetIP(); row["gm"] = cle->GetGM();
row["loginserver_account_id"] = cle->LSAccountID(); row["race"] = cle->race();
row["loginserver_id"] = cle->LSID(); row["class"] = cle->class_();
row["loginserver_name"] = cle->LSName(); row["client_version"] = cle->GetClientVersion();
row["online"] = cle->Online(); row["admin"] = cle->Admin();
row["world_admin"] = cle->WorldAdmin(); row["account_id"] = cle->AccountID();
row["account_name"] = cle->AccountName();
row["character_id"] = cle->CharID();
row["anon"] = cle->Anon();
row["guild_id"] = cle->GuildID();
if (full_list) {
row["loginserver_account_id"] = cle->LSAccountID();
row["loginserver_id"] = cle->LSID();
row["loginserver_name"] = cle->LSName();
row["online"] = cle->Online();
row["world_admin"] = cle->WorldAdmin();
row["guild_rank"] = cle->GuildRank();
row["guild_tribute_opt_in"] = cle->GuildTributeOptIn();
row["instance"] = cle->instance();
row["is_local_client"] = cle->IsLocalClient();
row["lfg"] = cle->LFG();
row["lfg_comments"] = cle->GetLFGComments();
row["lfg_from_level"] = cle->GetLFGFromLevel();
row["lfg_match_filter"] = cle->GetLFGMatchFilter();
row["lfg_to_level"] = cle->GetLFGToLevel();
row["tells_off"] = cle->TellsOff();
row["zone"] = cle->zone();
}
auto server = cle->Server(); auto server = cle->Server();
if (server) { if (server) {
row["server"]["client_address"] = server->GetCAddress(); row["server"]["zone_id"] = server->GetZoneID();
row["server"]["client_local_address"] = server->GetCLocalAddress(); row["server"]["zone_long_name"] = server->GetZoneLongName();
row["server"]["client_port"] = server->GetCPort(); row["server"]["zone_name"] = server->GetZoneName();
row["server"]["compile_time"] = server->GetCompileTime(); row["server"]["zone_os_pid"] = server->GetZoneOSProcessID();
row["server"]["id"] = server->GetID(); row["server"]["id"] = server->GetID();
row["server"]["instance_id"] = server->GetInstanceID();
row["server"]["ip"] = server->GetIP(); if (full_list) {
row["server"]["is_booting"] = server->IsBootingUp(); row["server"]["client_address"] = server->GetCAddress();
row["server"]["launch_name"] = server->GetLaunchName(); row["server"]["client_local_address"] = server->GetCLocalAddress();
row["server"]["launched_name"] = server->GetLaunchedName(); row["server"]["client_port"] = server->GetCPort();
row["server"]["number_players"] = server->NumPlayers(); row["server"]["compile_time"] = server->GetCompileTime();
row["server"]["port"] = server->GetPort(); row["server"]["instance_id"] = server->GetInstanceID();
row["server"]["previous_zone_id"] = server->GetPrevZoneID(); row["server"]["ip"] = server->GetIP();
row["server"]["static_zone"] = server->IsStaticZone(); row["server"]["is_booting"] = server->IsBootingUp();
row["server"]["uui"] = server->GetUUID(); row["server"]["launch_name"] = server->GetLaunchName();
row["server"]["zone_id"] = server->GetZoneID(); row["server"]["launched_name"] = server->GetLaunchedName();
row["server"]["zone_long_name"] = server->GetZoneLongName(); row["server"]["number_players"] = server->NumPlayers();
row["server"]["zone_name"] = server->GetZoneName(); row["server"]["port"] = server->GetPort();
row["server"]["zone_os_pid"] = server->GetZoneOSProcessID(); row["server"]["previous_zone_id"] = server->GetPrevZoneID();
row["server"]["static_zone"] = server->IsStaticZone();
row["server"]["uui"] = server->GetUUID();
}
} }
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,71 +1868,97 @@ 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)
{ {
std::vector<uint32_t> zone_server_ids; if (RuleB(World, RealTimeCalculateGuilds)) {
std::unordered_set<uint32_t> seen_ids; std::vector<uint32_t> zone_server_ids;
std::unordered_set<uint32_t> seen_ids;
LinkedListIterator<ClientListEntry *> iterator(clientlist); LinkedListIterator<ClientListEntry *> iterator(clientlist);
iterator.Reset(); iterator.Reset();
while (iterator.MoreElements()) { while (iterator.MoreElements()) {
ClientListEntry *cle = iterator.GetData(); ClientListEntry *cle = iterator.GetData();
if (cle->Online() != CLE_Status::InZone) { if (cle->Online() != CLE_Status::InZone) {
iterator.Advance(); iterator.Advance();
continue; continue;
}
if (!cle->Server()) {
iterator.Advance();
continue;
}
if (cle->GuildID() == guild_id) {
uint32_t id = cle->Server()->GetID();
if (seen_ids.insert(id).second) {
zone_server_ids.emplace_back(id);
} }
if (!cle->Server()) {
iterator.Advance();
continue;
}
if (cle->GuildID() == guild_id) {
uint32_t id = cle->Server()->GetID();
if (seen_ids.insert(id).second) {
zone_server_ids.emplace_back(id);
}
}
iterator.Advance();
} }
iterator.Advance(); return zone_server_ids;
} }
return zone_server_ids; auto it = m_guild_zone_server_ids.find(guild_id);
if (it == m_guild_zone_server_ids.end()) {
return {};
}
return {it->second.begin(), it->second.end()};
} }
std::vector<uint32_t> ClientList::GetZoneServersWithGMs() 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()) {
iterator.Advance();
continue;
}
if (cle->Admin() > 0) {
uint32_t id = cle->Server()->GetID();
if (seen_ids.insert(id).second) {
zone_server_ids.emplace_back(id);
}
}
iterator.Advance();
} }
return zone_server_ids; uint32_t server_id = cle->Server()->GetID();
// Add GM zone server if applicable
if (cle->GetGM()) {
m_gm_zone_server_ids.insert(server_id);
}
// Add guild zone server if applicable
if (cle->GuildID() > 0) {
m_guild_zone_server_ids[cle->GuildID()].insert(server_id);
}
} }
+15 -3
View File
@@ -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_*/
+20 -4
View File
@@ -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;
+1 -1
View File
@@ -286,7 +286,7 @@ void LoginServer::ProcessLSFatalError(uint16_t opcode, EQ::Net::Packet &p)
if (error.find("Worldserver Account / Password INVALID") != std::string::npos) { 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";
} }
} }
+1 -1
View File
@@ -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;
} }
+4
View File
@@ -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) {
+1
View File
@@ -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:
+18
View File
@@ -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()
);
}
}
+1
View File
@@ -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
View File
@@ -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 else if (my_owner) { // I am a pet, add other to owner if it's NPC/LD
if ( if (my_owner->IsAIControlled()) {
myowner->IsAIControlled() && bool aggro_immunity = my_owner->GetSpecialAbility(SpecialAbility::AggroImmunity);
!myowner->GetSpecialAbility(SpecialAbility::AggroImmunity) && bool bot_aggro_immunity = my_owner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity);
!(myowner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity)) && bool client_aggro_immunity = my_owner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity);
!(myowner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) && bool npc_aggro_immunity = my_owner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity);
!(myowner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity)) bool can_add_to_hatelist = !aggro_immunity &&
) { !bot_aggro_immunity &&
myowner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy); !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
View File
@@ -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;
} }
+347 -287
View File
@@ -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);
} }
@@ -2285,23 +2280,23 @@ void Bot::AI_Process()
} }
// COMBAT RANGE CALCS // COMBAT RANGE CALCS
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,
.p_item = p_item, .p_item = p_item,
.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);
return; if (ai_cast_successful) {
return;
}
} }
if (RuleB(Bots, UseSpellPulling)) {
uint16 pull_spell_id = RuleI(Bots, PullSpellID);
if (IsValidSpell(pull_spell_id) && tar_distance <= spells[pull_spell_id].range) {
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
SetPullingSpell(true);
CastSpell(pull_spell_id, tar->GetID());
SetPullingSpell(false);
}
}
return;
} }
if (RuleB(Bots, UseSpellPulling)) { TryPursueTarget(leash_distance);
uint16 spell_id = RuleI(Bots, PullSpellID);
if (tar_distance <= spells[spell_id].range) {
StopMoving();
SetPullingSpell(true);
CastSpell(spell_id, tar->GetID());
SetPullingSpell(false);
return;
}
}
TryPursueTarget(leash_distance, Goal);
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 ( if (!IsSitting() && !IsFacingMob(tar)) {
!jitter_cooldown && FaceTarget(tar);
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; return;
} }
else {
if (!IsSitting() && !IsFacingMob(tar)) {
FaceTarget(tar);
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) { RaidGroupSay(
const auto msg = fmt::format("Pulling {}.", pull_target->GetCleanName()); fmt::format(
raid->RaidSay(msg.c_str(), GetCleanName(), 0, 100); "Pulling {}.",
} else { pull_target->GetCleanName()
RaidGroupSay( ).c_str()
fmt::format( );
"Pulling {}.",
pull_target->GetCleanName()
).c_str()
);
}
InterruptSpell(); InterruptSpell();
WipeHateList(); WipeHateList();
@@ -3564,12 +3555,15 @@ 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);
m_previous_pet_order = GetPet()->GetPetOrder();
GetPet()->CastToNPC()->SaveGuardSpot(GetPosition()); if (HasControllablePet(BotAnimEmpathy::Guard)) {
GetPet()->SetPetOrder(SPO_Guard); m_previous_pet_order = GetPet()->GetPetOrder();
GetPet()->CastToNPC()->SaveGuardSpot(GetPosition());
GetPet()->SetPetOrder(SPO_Guard);
}
} }
} }
} }
@@ -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;
} FindPositionInput find_position_input = {
.tar = input.tar,
.distance_min = input.melee_distance_min,
.distance_max = input.melee_distance_max,
.behind_only = behind_mob_set,
.front_only = IsTaunting(),
.bypass_los = false,
};
bool is_melee = (!input.stop_melee_level && !IsBotRanged());
if (input.tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range
adjustment_needed =
(input.tar_distance <= input.melee_distance_max) &&
HasTargetReflection();
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_max + 1;
find_position_input.distance_max = input.melee_distance_max * 1.25f;
PlotBotPositionAroundTarget(find_position_input);
}
} else {
if (input.tar->IsFeared()) {
adjustment_needed = los_adjust;
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_min;
find_position_input.distance_max = input.melee_distance;
find_position_input.behind_only = false;
find_position_input.front_only = false;
PlotBotPositionAroundTarget(find_position_input);
} }
} }
else { else if (IsTaunting() || HasTargetReflection()) { // Taunting/Aggro adjustments
if (IsTaunting()) { // Taunting adjustments adjustment_needed =
bool taunting_adjust = (!front_mob || is_too_close || los_adjust); (IsTaunting() && is_too_close) ||
los_adjust ||
(is_melee && !input.front_mob);
if (taunting_adjust) { if (adjustment_needed && adjustment_allowed) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, true)) { find_position_input.distance_min = input.melee_distance_min;
RunToGoalWithJitter(Goal); find_position_input.distance_max = input.melee_distance;
find_position_input.behind_only = false;
find_position_input.front_only = true;
return; PlotBotPositionAroundTarget(find_position_input);
}
}
} }
else { } else {
if (tar->IsEnraged() && !stop_melee_level && !IsBotRanged()) { // Move non-taunting melee bots behind target during enrage if (input.tar->IsEnraged() && is_melee) { // Move non-taunting melee bots behind target during enrage
bool enraged_adjust = !behind_mob || is_too_close || los_adjust; adjustment_needed =
!behind_mob_set ||
is_too_close ||
los_adjust;
if (enraged_adjust) { if (adjustment_needed && adjustment_allowed) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) { find_position_input.distance_min = input.melee_distance_min;
RunToGoalWithJitter(Goal); find_position_input.distance_max = input.melee_distance;
find_position_input.behind_only = true;
find_position_input.front_only = false;
return; PlotBotPositionAroundTarget(find_position_input);
}
}
} }
else { // Regular adjustments } else { // Regular adjustments
bool regular_adjust = 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();
}
return adjustment_needed;
} }
bool Bot::PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behind_only, bool front_only, bool bypass_los) { bool Bot::PlotBotPositionAroundTarget(const FindPositionInput& input) {
bool Result = false; bool Result = false;
if (target) { if (input.tar) {
float look_heading = 0; glm::vec3 temp_goal(0, 0, input.tar->GetZ());
glm::vec3 tar_position(input.tar->GetX(), input.tar->GetY(), input.tar->GetZ());
min_distance = min_distance; float look_heading = 0;
max_distance = max_distance; float best_z = 0;
float temp_x = 0; float tar_distance = 0;
float temp_y = 0; float desired_angle = 0;
float temp_z = target->GetZ(); const float offset = GetZOffset();
float best_z = 0; const uint16 max_iterations_allowed = 50;
auto offset = GetZOffset(); uint16 counter = 0;
const float tar_x = target->GetX();
const float tar_y = target->GetY();
float tar_distance = 0;
glm::vec3 temp_z_Position;
glm::vec4 temp_m_Position;
const uint16 max_iterations_allowed = 50;
uint16 counter = 0;
while (counter < max_iterations_allowed) { 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
View File
@@ -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
View File
@@ -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()) {
+29 -121
View File
@@ -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;
} }
bot_iter->GetPet()->WipeHateList(); if (bot_iter->HasControllablePet(BotAnimEmpathy::BackOff)) {
bot_iter->GetPet()->SetTarget(nullptr); bot_iter->GetPet()->WipeHateList();
bot_iter->GetPet()->SetTarget(nullptr);
}
bot_iter->GetPet()->Teleport(c->GetPosition()); bot_iter->GetPet()->Teleport(c->GetPosition());
} }
+7 -1
View File
@@ -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.");
}
} }
+30 -61
View File
@@ -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,72 +76,37 @@ 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();
break;
case Class::Warrior:
case Class::ShadowKnight:
case Class::Paladin:
case Class::Berserker:
case Class::Beastlord:
if (!bot_puller) {
bot_puller = bot_iter; return;
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: default:
continue; break;
} }
if (!backup_puller_found) {
switch (bot_iter->GetClass()) {
case Class::Warrior:
case Class::ShadowKnight:
case Class::Paladin:
case Class::Berserker:
case Class::Beastlord:
backup_bot_puller = bot_iter;
backup_puller_found = true;
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();
} }
+6
View File
@@ -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);
} }
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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()) {
+56 -16
View File
@@ -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
+60
View File
@@ -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
View File
@@ -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;
+61 -67
View File
@@ -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("_{}", Strings::ToLower(GetClassIDName(class_id))) :
fmt::format(
"_{}",
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,63 +47,55 @@ 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("_{}", Strings::ToLower(GetClassIDName(class_id))) :
fmt::format(
"_{}",
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("_{}", Strings::ToLower(GetClassIDName(class_id))) :
fmt::format(
"_{}",
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)) {
bot_spawn_limit = Strings::ToInt(bucket_value);
return bot_spawn_limit;
}
}
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);
} }
if (class_id && bucket_value.empty()) {
return NO_BOT_LIMIT;
}
if (RuleB(Bots, QuestableSpawnLimit)) { if (RuleB(Bots, QuestableSpawnLimit)) {
const auto query = fmt::format( const auto query = fmt::format(
"SELECT `value` FROM `quest_globals` WHERE `name` = '{}' AND `charid` = {} LIMIT 1", "SELECT `value` FROM `quest_globals` WHERE `name` = '{}' AND `charid` = {} LIMIT 1",
@@ -111,53 +105,53 @@ 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();
bot_spawn_limit = Strings::ToInt(row[0]);
} }
auto row = results.begin();
bot_spawn_limit = Strings::ToInt(row[0]);
} }
const auto& zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ","); if (!class_id) {
const auto &zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ",");
if (!zones_list.empty()) { if (!zones_list.empty()) {
auto it = std::find(zones_list.begin(), zones_list.end(), std::to_string(zone->GetZoneID())); auto it = std::find(zones_list.begin(), zones_list.end(), std::to_string(zone->GetZoneID()));
if (it != zones_list.end()) { if (it != zones_list.end()) {
const auto& zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ","); const auto &zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ",");
if (zones_list.size() == zones_limits_list.size()) { if (zones_list.size() == zones_limits_list.size()) {
try { try {
auto new_limit = std::stoul(zones_limits_list[std::distance(zones_list.begin(), it)]); auto new_limit = std::stoul(zones_limits_list[std::distance(zones_list.begin(), it)]);
if (new_limit < bot_spawn_limit) { if (new_limit < bot_spawn_limit) {
bot_spawn_limit = new_limit; bot_spawn_limit = new_limit;
}
} catch (const std::exception &e) {
LogInfo("Invalid entry in Rule Bots:ZoneSpawnLimits: [{}]", e.what());
} }
} catch (const std::exception& e) {
LogInfo("Invalid entry in Rule Bots:ZoneSpawnLimits: [{}]", e.what());
} }
} }
} }
}
const auto& zones_forced_list = Strings::Split(RuleS(Bots, ZonesWithForcedSpawnLimits), ","); const auto &zones_forced_list = Strings::Split(RuleS(Bots, ZonesWithForcedSpawnLimits), ",");
if (!zones_forced_list.empty()) { if (!zones_forced_list.empty()) {
auto it = std::find(zones_forced_list.begin(), zones_forced_list.end(), std::to_string(zone->GetZoneID())); auto it = std::find(zones_forced_list.begin(), zones_forced_list.end(), std::to_string(zone->GetZoneID()));
if (it != zones_forced_list.end()) { if (it != zones_forced_list.end()) {
const auto& zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ","); const auto &zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ",");
if (zones_forced_list.size() == zones_forced_limits_list.size()) { if (zones_forced_list.size() == zones_forced_limits_list.size()) {
try { try {
auto new_limit = std::stoul(zones_forced_limits_list[std::distance(zones_forced_list.begin(), it)]); auto new_limit = std::stoul(zones_forced_limits_list[std::distance(zones_forced_list.begin(), it)]);
if (new_limit != bot_spawn_limit) { if (new_limit != bot_spawn_limit) {
bot_spawn_limit = new_limit; bot_spawn_limit = new_limit;
}
} catch (const std::exception &e) {
LogInfo("Invalid entry in Rule Bots:ZoneForcedSpawnLimits: [{}]", e.what());
} }
} catch (const std::exception& e) {
LogInfo("Invalid entry in Rule Bots:ZoneForcedSpawnLimits: [{}]", e.what());
} }
} }
} }
+23 -6
View File
@@ -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;
} }
+54 -20
View File
@@ -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,14 +1327,14 @@ 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);
for (auto row : results) { for (auto row : results) {
if (row[4] && Strings::ToInt(row[4]) > 0) { if (row[4] && Strings::ToInt(row[4]) > 0) {
guild_id = Strings::ToInt(row[4]); guild_id = Strings::ToInt(row[4]);
guildrank = row[5] ? Strings::ToInt(row[5]) : GUILD_RANK_NONE; guildrank = row[5] ? Strings::ToInt(row[5]) : GUILD_RANK_NONE;
guild_tribute_opt_in = row[7] ? Strings::ToBool(row[7]) : 0; guild_tribute_opt_in = row[7] ? Strings::ToBool(row[7]) : 0;
} }
@@ -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);
+3 -1
View File
@@ -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();
+2
View File
@@ -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"
+1
View File
@@ -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"
+3 -1
View File
@@ -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
View File
@@ -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;
+8
View File
@@ -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;
} }
+30 -1
View File
@@ -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();
} }
@@ -2335,7 +2338,7 @@ void EntityList::QueueClientsGuild(const EQApplicationPacket *app, uint32 guild_
void EntityList::QueueClientsGuildBankItemUpdate(GuildBankItemUpdate_Struct *gbius, uint32 guild_id) void EntityList::QueueClientsGuildBankItemUpdate(GuildBankItemUpdate_Struct *gbius, uint32 guild_id)
{ {
auto outapp = std::make_unique<EQApplicationPacket>(OP_GuildBank, sizeof(GuildBankItemUpdate_Struct)); auto outapp = std::make_unique<EQApplicationPacket>(OP_GuildBank, sizeof(GuildBankItemUpdate_Struct));
auto data = reinterpret_cast<GuildBankItemUpdate_Struct *>(outapp->pBuffer); auto data = reinterpret_cast<GuildBankItemUpdate_Struct *>(outapp->pBuffer);
memcpy(data, gbius, sizeof(GuildBankItemUpdate_Struct)); memcpy(data, gbius, sizeof(GuildBankItemUpdate_Struct));
@@ -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()
);
}
}
+1
View File
@@ -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;
+18
View File
@@ -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;
+5
View File
@@ -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]");
+100
View File
@@ -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());
}
}
+1
View File
@@ -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();
} }
} }
+2 -2
View File
@@ -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) {
+7
View File
@@ -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)
+2
View File
@@ -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
+1
View File
@@ -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;
+20
View File
@@ -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,
+9
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+1
View File
@@ -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.
+27 -26
View File
@@ -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();
@@ -1285,23 +1287,24 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName)); strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName));
} }
ns->spawn.heading = FloatToEQ12(m_Position.w); ns->spawn.heading = FloatToEQ12(m_Position.w);
ns->spawn.x = FloatToEQ19(m_Position.x);//((int32)x_pos)<<3; ns->spawn.x = FloatToEQ19(m_Position.x); //((int32)x_pos)<<3;
ns->spawn.y = FloatToEQ19(m_Position.y);//((int32)y_pos)<<3; ns->spawn.y = FloatToEQ19(m_Position.y); //((int32)y_pos)<<3;
ns->spawn.z = FloatToEQ19(m_Position.z);//((int32)z_pos)<<3; ns->spawn.z = FloatToEQ19(m_Position.z); //((int32)z_pos)<<3;
ns->spawn.spawnId = GetID(); ns->spawn.spawnId = GetID();
ns->spawn.curHp = static_cast<uint8>(GetHPRatio()); ns->spawn.curHp = static_cast<uint8>(GetHPRatio());
ns->spawn.max_hp = 100; //this field needs a better name ns->spawn.max_hp = 100; // this field needs a better name
ns->spawn.race = (use_model) ? use_model : race; ns->spawn.race = (use_model) ? use_model : race;
ns->spawn.runspeed = runspeed; ns->spawn.runspeed = runspeed;
ns->spawn.walkspeed = walkspeed; ns->spawn.walkspeed = walkspeed;
ns->spawn.class_ = class_; ns->spawn.class_ = class_;
ns->spawn.gender = gender; ns->spawn.gender = gender;
ns->spawn.level = level; ns->spawn.level = level;
ns->spawn.PlayerState = GetPlayerState(); ns->spawn.PlayerState = GetPlayerState();
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
View File
@@ -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; }
+4 -9
View File
@@ -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)
+14 -8
View File
@@ -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);
@@ -1256,10 +1258,11 @@ uint32 ZoneDatabase::CreateNewNPCCommand(
e.Avoidance = n->GetAvoidanceRating(); e.Avoidance = n->GetAvoidanceRating();
e.heroic_strikethrough = n->GetHeroicStrikethrough(); e.heroic_strikethrough = n->GetHeroicStrikethrough();
e.see_hide = n->SeeHide(); e.see_hide = n->SeeHide();
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);
@@ -2169,9 +2174,10 @@ void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
PetOnSpawn(ns); PetOnSpawn(ns);
ns->spawn.is_npc = 1; ns->spawn.is_npc = 1;
UpdateActiveLight(); UpdateActiveLight();
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)
+18
View File
@@ -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);
} }
+7
View File
@@ -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(
+6
View File
@@ -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
View File
@@ -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
View File
@@ -699,7 +699,7 @@ void QuestManager::stoptimer(const std::string& timer_name, Mob* m)
} }
for (auto e = QTimerList.begin(); e != QTimerList.end(); ++e) { 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
View File
@@ -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;
+2
View File
@@ -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
View File
@@ -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){
+11 -16
View File
@@ -5359,16 +5359,17 @@ 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)
int resist_bonuses = CalcResistChanceBonus(); if (!spells[spell_id].no_resist && resist_type != RESIST_NONE) {
if (resist_bonuses && zone->random.Roll(resist_bonuses) && !IsResurrectionSicknessSpell(spell_id)) { int resist_bonuses = CalcResistChanceBonus();
LogSpells("Resisted spell in sanctification, had [{}] chance to resist", resist_bonuses); if (resist_bonuses && zone->random.Roll(resist_bonuses) && !IsResurrectionSicknessSpell(spell_id)) {
return 0; LogSpells("Resisted spell in sanctification, had [{}] chance to resist", resist_bonuses);
return 0;
}
} }
} }
@@ -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)
+23 -11
View File
@@ -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) {
LogError("Error in EnableTitle query for titleset [{}] and charid [{}]", title_set, CharacterID()); e = PlayerTitlesetsRepository::InsertOne(database, e);
if (!e.id) {
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(
+8 -3
View File
@@ -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)) {
+43 -29
View File
@@ -1351,12 +1351,12 @@ 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;
if (ClientVersion() >= EQ::versions::ClientVersion::RoF) { if (ClientVersion() >= EQ::versions::ClientVersion::RoF) {
tbs->item_id = Strings::ToUnsignedBigInt(tbs->serial_number); tbs->item_id = Strings::ToUnsignedBigInt(tbs->serial_number);
@@ -1557,15 +1557,15 @@ 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