Compare commits

..

110 Commits

Author SHA1 Message Date
Chris Miles cb866cba31 [Release] 23.7.0 (#4902) 2025-05-19 12:31:37 -05:00
JJ e2162c08da [CLI] Add custom database version output (#4901)
* Add custom database version output to CLI from #4892

* Spacing alignment
2025-05-18 11:28:10 -05:00
Chris Miles e657953b8f [Netcode] Resend Logic Adjustments (#4900)
* Timeout adjustment

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Stuff

* Update daybreak_connection.h

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp

* Update daybreak_connection.cpp
2025-05-18 11:19:37 -05:00
JJ eb366e67b7 [CLI] Fix MySQL check in database dumper (#4897) 2025-05-16 18:56:38 -05:00
JJ 5b728a42f7 Update zonelist.h (#4896) 2025-05-16 18:17:38 -04:00
Chris Miles a1d414d64c [Netcode] Resend Logic Adjustments (#4895)
* [Netcode] Resend Logic Adjustments

* Update daybreak_connection.h
2025-05-16 16:46:07 -05:00
Chris Miles 907029ed76 [Database] Remove Transaction Wrapped Character Save (#4894) 2025-05-16 16:07:45 -05:00
Chris Miles 894f22fba0 [Fix] Deadlock on failed #copycharacter commands (#4887) 2025-05-16 13:39:34 -05:00
Chris Miles 7ec09d7e0f [Player Events] Add rule to ignore configured GM commands (#4888) 2025-05-16 13:39:24 -05:00
Chris Miles c82266790a [Zone State] Load New Spawn2 Data When Present (#4889) 2025-05-16 13:39:16 -05:00
Chris Miles ec31fddbae [Logging] Auto Update Log Category Names (#4890) 2025-05-16 13:39:08 -05:00
Chris Miles fb49ce2404 [Rules] Auto Update Rule Notes from Source (#4891)
* [Rules] Auto Update Rule Notes from Source

* Update rulesys.cpp
2025-05-16 13:38:59 -05:00
Chris Miles 276b7e238a [Database] Add Custom Database Migrations for Operators (#4892)
* [Database] Add Custom Database Migrations for Operators

* Changes

* Update database_update_manifest_custom.cpp
2025-05-16 13:38:51 -05:00
Chris Miles c7a463420b [World] Fix Rarer Reload Deadlock (#4893) 2025-05-16 13:38:42 -05:00
Chris Miles a56bb52808 Update README.md 2025-05-15 15:29:05 -05:00
Chris Miles 0bbdb58679 Update README.md 2025-05-15 15:26:07 -05:00
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
Chris Miles 5babc864b9 [Fix] Regression in World SendEmoteMessageRaw (#4837) 2025-04-03 11:17:09 -05:00
Chris Miles 60a2dd8616 [Crash] Fix rarer exception crash issue in PlayerEventLogs::ProcessBatchQueue (#4835) 2025-04-02 19:22:40 -05:00
Akkadius 9be2485330 [Hotfix] Backfill expire_at (not sure why this didn't make it in there to begin with) 2025-03-31 02:34:35 -05:00
Akkadius a2bf10624a Update database_update_manifest.cpp 2025-03-31 02:29:25 -05:00
Akkadius ed58d16f1f [Hotfix] Make sure we don't expire default value instances 2025-03-31 02:25:07 -05:00
Akkadius e9b45fb360 Update CHANGELOG.md 2025-03-30 17:05:27 -05:00
Chris Miles 803972873a [Database] Wrap PurgeExpiredInstances in a Transaction (#4824) 2025-03-30 17:03:57 -05:00
Chris Miles d9e57eca79 [Fix] Instance DZ Creation (#4823)
* [Fix] Instance DZ Creation

* Update dynamic_zone_base.cpp
2025-03-30 16:59:29 -05:00
JJ 6429dc80d3 [Release] 23.4.0 (#4822)
* Update CHANGELOG.md

* Update package.json

* Update version.h
2025-03-30 16:12:42 -05:00
JJ c4262b3fa6 [Cleanup] UCS Member Count (#4819)
* [Cleanup] Fix SendChannelMembers size in UCS ChatChannelList

* Part 2

* Forgot null terminator
2025-03-30 14:46:46 -05:00
Chris Miles 92128b98fd [Instances] Add expire_at Column (#4820)
* [Instances] Add `expire_at` Column

* expire_at update

* Update servertalk.h

* Add rule Instances:ExpireOffsetTimeSeconds
2025-03-30 14:46:02 -05:00
Chris Miles b9cfdea76c [Zone] Zone State Automated Testing and Improvements (#4808)
* [Zone] Zone State Automated Testing and Improvements

* Spawn condition

* Update zone.cpp

* Remove redundant logic

* Update zone_state.cpp

* TestZLocationDrift

* Protect NPC resumed NPC's from being able to die
2025-03-30 01:45:28 -05:00
Chris Miles c8a7066d0e [Performance] Send Smarter Emote Packets (#4818) 2025-03-29 19:50:44 -05:00
JJ 950cc4a325 [Cleanup] Control flow defaults missed in recent bot updates (#4817) 2025-03-29 19:07:31 -05:00
Chris Miles 82b48fe6e8 [Reload] Add Reload for Maps / Navs (#4816) 2025-03-29 18:24:17 -05:00
Chris Miles ca9c1fdd24 [API] Expose Zoneserver Compile Metadata (#4815) 2025-03-29 18:14:02 -05:00
Chris Miles fe08961d25 [Crash] Fix Repop Race Condition Crash (#4814)
* [Crash] Fix Repop Race Condition Crash

* True fix
2025-03-29 17:39:40 -05:00
Chris Miles 5b9f7ff4c9 [Fix] Globally Reloading Quests when not loaded (#4813) 2025-03-29 17:10:40 -05:00
Chris Miles 23743a4050 [Fix] Fix Instance Creation Race Condition (#4803)
* [Fix] Fix Instance Creation Race Condition

* Rejigger

* Update database_instances.cpp

* Update database_instances.cpp

* Update database_instances.cpp

* Update database_instances.cpp
2025-03-29 17:10:20 -05:00
nytmyr 444d688ad2 [Bots] Line of Sight and Mez optimizations and cleanup (#4746)
* [Bots] Line of Sight and Mez optimizations and cleanup

- Renames `Map:CheckForLoSCheat` to `Map:CheckForDoorLoSCheat` to better reflect what it does.
- Renames `Map:RangeCheckForLoSCheat` to `Map:RangeCheckForDoorLoSCheat` to better reflect what it does.
- Adds the rule `Pets:PetsRequireLoS` to determine whether or not commanded pet attacks require an addition layer of LoS checks for edge-cases.
- Adds the rule `Bots:BotsRequireLoS` to determine whether or not bots require LoS to `^attack`, `^pull` and `^precombat`.
- Adds the rule `Map:ZonesToCheckDoorCheat` to control what if any zones will be checked..
- Corrects, removes and adds LoS checks where necessary.
- Improves door checking logic for locked or triggered doors that could be blocking LoS.
- Cleans up false positives for door cheat checks.
- Adds `drawbox` option to `#door` command. This will spawn points at the center and each corner of the door's "box". It will also spawn points at your and your target's location.
- Improves Mez and AE Mez logic
- Adds more details to the rule `Bots:EpicPetSpellName`

* Remove leftover debugging

* Change return to continue for GetFirstIncomingMobToMez checks

* Move mez chance fail to beginning of cast process
2025-03-29 16:01:31 -05:00
nytmyr d554eb3423 [Bots] Fix rule Bots:FinishBuffing (#4788) 2025-03-29 15:17:33 -05:00
nytmyr deb298dda7 [Bots] Enraged positioning (#4789)
- Non-taunting melee bots will now properly go behind their target when it is enraged.
- Reduces how often taunting bots adjust their positioning by removing unnecessary rules.
- Cleans up CombatPositioning a bit
2025-03-29 15:00:08 -05:00
nytmyr 19e785b842 [Bots] Fix Rule ZonesWithSpawnLimits/ZonesWithForcedSpawnLimits errors (#4791)
* [Bots] Fix error copy/paste

Oops

* Eliminate false errors on empty rules and add more sanity checks

- `Bots:ZonesWithSpawnLimits` - This is to be used when zones will only allow up to x amount of bots. Example: A player can normally spawn 5 bots but a zone in this rule has a lower limit of up to 3, this rule would override their normal limit if their normal limit is above the listed zone's max in `Bots:ZoneSpawnLimits`.
- `Bots:ZonesWithForcedSpawnLimits` - Zones in this rule will override any spawn limits high or low and force it. If one player can normally spawn 2 and another player can spawn 10 but a zone listed forces a limit of 5, all players will be able to spawn 5. Follows the limits set in `Bots:ZoneForcedSpawnLimits`
2025-03-29 14:59:09 -05:00
Chris Miles 7b9691d486 [Performance] Reduce LFGuild Chatter (#4794) 2025-03-29 14:58:01 -05:00
Chris Miles 30fddcc5a0 [Performance] Reduce UpdateWho S2S Chatter to World (#4792)
* [Performance] Reduce UpdateWho S2S chatter

* Add rule to change this dynamically
2025-03-29 14:56:32 -05:00
Chris Miles 235e59a2d8 [Database] Fix Respawn Times Table (#4802) 2025-03-29 14:54:26 -05:00
Chris Miles bc1ffe0716 [Performance] Reduce Adventure S2S chatter (#4793) 2025-03-29 14:48:41 -05:00
Chris Miles 44497414db [Instance] Clear Respawn Timers on Creation (#4801)
* [DZ] Clear Respawn Timers on Creation

* Revert "[DZ] Clear Respawn Timers on Creation"

This reverts commit ae18b77e83.

* Clear respawn times on instance creation
2025-03-29 14:47:25 -05:00
Chris Miles 96e34fe8f7 [Performance] Improve Character Select DB Performance (#4799) 2025-03-29 14:46:57 -05:00
Alex King ceca28d2a3 [Cleanup] Remove Extraneous Time Type in ShowZoneData (#4806) 2025-03-29 14:44:26 -05:00
Alex King b040571427 [Commands] Add Instance Support to #zoneshutdown (#4807)
* [Commands] Add Instance Support to #zoneshutdown

* Update zoneserver.cpp

* Update zoneshutdown.cpp
2025-03-29 14:44:08 -05:00
Chris Miles 49664cc1a0 [Performance] Reduce CorpseOwnerOnline S2S Chatter to World (#4795) 2025-03-29 14:30:00 -05:00
nytmyr 5e4fd43920 [Bots] Prevent bot pets from despawning on #repop (#4790) 2025-03-29 14:29:29 -05:00
Chris Miles 937b947597 [Performance] Have World Send Smarter Guild Updates (#4796)
* [Performance] Have World Send Smarter Guild Updates

* Updates to correct incorrect guild window details (permissions, etc) not being sent on guild creation.

---------

Co-authored-by: Mitch Freeman <65987027+neckkola@users.noreply.github.com>
2025-03-29 14:27:49 -05:00
Chris Miles bb70850421 [Fix] Zone State Variables Load First (#4798) 2025-03-29 14:26:12 -05:00
Chris Miles 46511365a7 [Crash] Fix Rarer World Crash with Player Event Thread Processor (#4800)
* [Crash] Fix Rarer World Crash with Player Event thread processor

* Update main.cpp
2025-03-29 14:26:00 -05:00
Chris Miles 6d69ac7a98 [Performance] Add several database indexes (#4811)
* [Performance] Add several database indexes

* Update database_update_manifest.cpp

* Update database_update_manifest.cpp

* Push
2025-03-29 14:23:28 -05:00
Alex King 938937c271 [Cleanup] Remove Unused Command Methods (#4805)
* [Cleanup] Remove Unused Command Methods

* Update command.h
2025-03-29 14:23:10 -05:00
Chris Miles cd808416c8 [Command] Add #show zone_variables (#4812) 2025-03-29 14:22:14 -05:00
Chris Miles a05d0752f6 [Fix] Zone state edge case with 0 hp (#4787) 2025-03-29 14:20:23 -05:00
Mitch Freeman 799609fb21 [Fix] AllowFVNoDrop Flag trades (#4809) 2025-03-26 21:38:13 -04:00
164 changed files with 5360 additions and 2191 deletions
+238
View File
@@ -1,3 +1,241 @@
## [23.7.0] 5/19/2025
### CLI
* Add custom database version output ([#4901](https://github.com/EQEmu/Server/pull/4901)) @joligario 2025-05-18
* Fix MySQL check in database dumper ([#4897](https://github.com/EQEmu/Server/pull/4897)) @joligario 2025-05-16
### Commands
* Add #zonevariable Command ([#4882](https://github.com/EQEmu/Server/pull/4882)) @Kinglykrab 2025-05-15
### Database
* Add Custom Database Migrations for Operators ([#4892](https://github.com/EQEmu/Server/pull/4892)) @Akkadius 2025-05-16
* Remove Transaction Wrapped Character Save ([#4894](https://github.com/EQEmu/Server/pull/4894)) @Akkadius 2025-05-16
### Fixes
* Deadlock on failed #copycharacter commands ([#4887](https://github.com/EQEmu/Server/pull/4887)) @Akkadius 2025-05-16
### Logging
* Auto Update Log Category Names ([#4890](https://github.com/EQEmu/Server/pull/4890)) @Akkadius 2025-05-16
### Netcode
* Resend Logic Adjustments ([#4900](https://github.com/EQEmu/Server/pull/4900)) @Akkadius 2025-05-18
### Player Events
* Add rule to ignore configured GM commands ([#4888](https://github.com/EQEmu/Server/pull/4888)) @Akkadius 2025-05-16
### Rules
* Auto Update Rule Notes from Source ([#4891](https://github.com/EQEmu/Server/pull/4891)) @Akkadius 2025-05-16
### World
* Fix Rarer Reload Deadlock ([#4893](https://github.com/EQEmu/Server/pull/4893)) @Akkadius 2025-05-16
### Zone State
* Load New Spawn2 Data When Present ([#4889](https://github.com/EQEmu/Server/pull/4889)) @Akkadius 2025-05-16
## [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
### API
* Expose Zoneserver Compile Metadata ([#4815](https://github.com/EQEmu/Server/pull/4815)) @Akkadius 2025-03-29
### Bots
* Charmed Pets were breaking Mob respawns ([#4780](https://github.com/EQEmu/Server/pull/4780)) @nytmyr 2025-03-16
* Enraged positioning ([#4789](https://github.com/EQEmu/Server/pull/4789)) @nytmyr 2025-03-29
* Fix IsValidSpellTypeBySpellID to account for all types ([#4764](https://github.com/EQEmu/Server/pull/4764)) @nytmyr 2025-03-19
* Fix Rule ZonesWithSpawnLimits/ZonesWithForcedSpawnLimits errors ([#4791](https://github.com/EQEmu/Server/pull/4791)) @nytmyr 2025-03-29
* Fix rule Bots:FinishBuffing ([#4788](https://github.com/EQEmu/Server/pull/4788)) @nytmyr 2025-03-29
* Line of Sight and Mez optimizations and cleanup ([#4746](https://github.com/EQEmu/Server/pull/4746)) @nytmyr 2025-03-29
* Prevent bot pets from despawning on #repop ([#4790](https://github.com/EQEmu/Server/pull/4790)) @nytmyr 2025-03-29
### Code
* Control flow defaults missed in recent bot updates ([#4817](https://github.com/EQEmu/Server/pull/4817)) @joligario 2025-03-30
* Remove Extraneous Time Type in ShowZoneData ([#4806](https://github.com/EQEmu/Server/pull/4806)) @Kinglykrab 2025-03-29
* Remove Unused Command Methods ([#4805](https://github.com/EQEmu/Server/pull/4805)) @Kinglykrab 2025-03-29
* UCS Member Count ([#4819](https://github.com/EQEmu/Server/pull/4819)) @joligario 2025-03-30
### Commands
* Add #show zone_variables ([#4812](https://github.com/EQEmu/Server/pull/4812)) @Akkadius 2025-03-29
* Add Instance Support to #zoneshutdown ([#4807](https://github.com/EQEmu/Server/pull/4807)) @Kinglykrab 2025-03-29
### Crash
* Fix Rarer World Crash with Player Event Thread Processor ([#4800](https://github.com/EQEmu/Server/pull/4800)) @Akkadius 2025-03-29
* Fix Repop Race Condition Crash ([#4814](https://github.com/EQEmu/Server/pull/4814)) @Akkadius 2025-03-29
### Database
* Fix Respawn Times Table ([#4802](https://github.com/EQEmu/Server/pull/4802)) @Akkadius 2025-03-29
* Wrap PurgeExpiredInstances in a Transaction ([#4824](https://github.com/EQEmu/Server/pull/4824)) @Akkadius 2025-03-30
### Feature
* Implement /changename & related script bindings. Clean up #set name ([#4770](https://github.com/EQEmu/Server/pull/4770)) @catapultam-habeo 2025-03-20
### Fixes
* AllowFVNoDrop Flag trades ([#4809](https://github.com/EQEmu/Server/pull/4809)) @neckkola 2025-03-27
* Fix Instance Creation Race Condition ([#4803](https://github.com/EQEmu/Server/pull/4803)) @Akkadius 2025-03-29
* Fix zone crash when attempting to add a disappearing client to hate list. ([#4782](https://github.com/EQEmu/Server/pull/4782)) @zimp-wow 2025-03-19
* Globally Reloading Quests when not loaded ([#4813](https://github.com/EQEmu/Server/pull/4813)) @Akkadius 2025-03-29
* Instance DZ Creation ([#4823](https://github.com/EQEmu/Server/pull/4823)) @Akkadius 2025-03-30
* Zone State Entity Variable Load Pre-Spawn ([#4785](https://github.com/EQEmu/Server/pull/4785)) @Akkadius 2025-03-19
* Zone State Position Fix ([#4784](https://github.com/EQEmu/Server/pull/4784)) @Akkadius 2025-03-19
* Zone State Variables Load First ([#4798](https://github.com/EQEmu/Server/pull/4798)) @Akkadius 2025-03-29
* Zone state edge case with 0 hp ([#4787](https://github.com/EQEmu/Server/pull/4787)) @Akkadius 2025-03-29
### Instance
* Clear Respawn Timers on Creation ([#4801](https://github.com/EQEmu/Server/pull/4801)) @Akkadius 2025-03-29
### Instances
* Add `expire_at` Column ([#4820](https://github.com/EQEmu/Server/pull/4820)) @Akkadius 2025-03-30
### Performance
* Add several database indexes ([#4811](https://github.com/EQEmu/Server/pull/4811)) @Akkadius 2025-03-29
* Have World Send Smarter Guild Updates ([#4796](https://github.com/EQEmu/Server/pull/4796)) @Akkadius 2025-03-29
* Improve Character Select DB Performance ([#4799](https://github.com/EQEmu/Server/pull/4799)) @Akkadius 2025-03-29
* Reduce Adventure S2S chatter ([#4793](https://github.com/EQEmu/Server/pull/4793)) @Akkadius 2025-03-29
* Reduce CorpseOwnerOnline S2S Chatter to World ([#4795](https://github.com/EQEmu/Server/pull/4795)) @Akkadius 2025-03-29
* Reduce LFGuild Chatter ([#4794](https://github.com/EQEmu/Server/pull/4794)) @Akkadius 2025-03-29
* Reduce UpdateWho S2S Chatter to World ([#4792](https://github.com/EQEmu/Server/pull/4792)) @Akkadius 2025-03-29
* Send Smarter Emote Packets ([#4818](https://github.com/EQEmu/Server/pull/4818)) @Akkadius 2025-03-30
### Quest API
* Add Support for NPC ID and NPC Name Specificity ([#4781](https://github.com/EQEmu/Server/pull/4781)) @Kinglykrab 2025-03-19
### Reload
* Add Reload for Maps / Navs ([#4816](https://github.com/EQEmu/Server/pull/4816)) @Akkadius 2025-03-29
### Zone
* Zone State Automated Testing and Improvements ([#4808](https://github.com/EQEmu/Server/pull/4808)) @Akkadius 2025-03-30
* Zone State Improvements Part 3 ([#4773](https://github.com/EQEmu/Server/pull/4773)) @Akkadius 2025-03-13
## [23.3.4] 3/14/2025
### Fixes
+123 -55
View File
@@ -1,79 +1,147 @@
# EQEmulator Core Server
| Drone (Linux x64) | Drone (Windows x64) |
|:---:|:---:|
|[![Build Status](http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg)](http://drone.akkadius.com/EQEmu/Server) |[![Build Status](http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg)](http://drone.akkadius.com/EQEmu/Server) |
<h1 align="center">EQEmulator Server Platform</h1>
<p align="center">
<img src="https://github.com/user-attachments/assets/11942e15-b512-402d-a619-0543c7f1151e" style="border-radius: 10px">
</p>
<p align="center">
<b>EverQuest Emulator (EQEmu) - A Fan-Made Project Honoring the Legendary MMORPG</b>
</p>
<p align="center">
<a href="https://github.com/eqemu/server/graphs/contributors"><img src="https://img.shields.io/github/contributors/eqemu/server" alt="Contributors"></a>
<a href="https://discord.gg/QHsm7CD"><img src="https://img.shields.io/discord/212663220849213441?label=Discord&amp;logo=discord&amp;color=7289DA" alt="Discord"></a>
<a href="https://docs.eqemu.io"><img src="https://img.shields.io/badge/docs-MkDocs%20Powered-blueviolet" alt="Docs"></a>
<a href="./LICENSE"><img src="https://img.shields.io/github/license/EQEmu/Server" alt="License"></a>
<a href="https://github.com/eqemu/server/releases"><img src="https://img.shields.io/github/v/release/eqemu/server" alt="Latest Release"></a>
<a href="https://github.com/EQEmu/Server/releases"><img src="https://img.shields.io/github/release-date/EQEmu/Server" alt="Release Date"></a>
<img src="https://img.shields.io/github/downloads/eqemu/server/total.svg" alt="Github All Releases"></a>
<a href="http://drone.akkadius.com/EQEmu/Server"><img src="http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg" alt="Build Status"></a>
<img src="https://img.shields.io/github/issues-pr-closed/eqemu/server" alt="GitHub Issues or Pull Requests">
<img src="https://img.shields.io/docker/pulls/akkadius/eqemu-server" alt="Docker Pulls">
<a href="http://drone.akkadius.com/EQEmu/Server"><img src="http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg" alt="Build Status"></a> <img src="https://jb.gg/badges/official-plastic.svg" alt="Official">
</p>
***
**EQEmulator is a custom completely from-scratch open source server implementation for EverQuest built mostly on C++**
* MySQL/MariaDB is used as the database engine (over 200+ tables)
* Perl and LUA are both supported scripting languages for NPC/Player/Quest oriented events
* Open source database (Project EQ) has content up to expansion OoW (included in server installs)
* Game server environments and databases can be heavily customized to create all new experiences
* Hundreds of Quests/events created and maintained by Project EQ
<p align="center">
EQEmulator is a <b>passion-driven</b>, <b>open source server emulator</b> project dedicated to preserving and celebrating the groundbreaking world of <b>EverQuest</b>, the massively multiplayer online role-playing game originally developed by <b>Verant Interactive</b> and <b>Sony Online Entertainment (now Daybreak Game Company)</b>.
</p>
## Server Installs
| |Windows|Linux|
|:---:|:---:|:---:|
|**Install Count**|![Windows Install Count](http://analytics.akkadius.com/?install_count&windows_count)|![Linux Install Count](http://analytics.akkadius.com/?install_count&linux_count)|
### > Windows
<p align="center">
For over two decades and continuing, EQEmulator has served as a <strong>fan tribute</strong>, providing tools and technology that allow players to explore, customize, and experience EverQuests iconic gameplay in new ways. This project exists solely out of <strong>deep admiration</strong> for the original developers, artists, designers, and visionaries who created one of the most influential online worlds of all time.
</p>
* [Install Guide](https://docs.eqemu.io/server/installation/server-installation-windows/)
<p align="center">
We do not claim ownership of EverQuest or its assets. <strong>All credit and respect belong to the original creators and Daybreak Game Company</strong>, whose work continues to inspire generations of players and developers alike.
</p>
### > Debian/Ubuntu/CentOS/Fedora
<p align="center">
EQEmulator has for over 20 years and always will be a <strong>fan-based, non-commercial open-source effort</strong> made by players, for players—preserving the legacy of EverQuest while empowering community-driven creativity, learning and joy that the game and its creators has so strongly inspired in us all.
</p>
* [Install Guide](https://docs.eqemu.io/server/installation/server-installation-linux/)
***
* You can use curl or wget to kick off the installer (whichever your OS has)
> curl -O https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh install.sh && chmod 755 install.sh && ./install.sh
<h3 align="center">
Technical Overview & Reverse Engineering Effort
</h1>
> wget --no-check-certificate https://raw.githubusercontent.com/EQEmu/Server/master/utils/scripts/linux_installer/install.sh -O install.sh && chmod 755 install.sh && ./install.sh
<p align="center">EQEmulator represents <strong>over two decades of collaborative reverse engineering</strong>, rebuilding the EverQuest server from the ground up without access to the original source code. This effort was achieved entirely through <strong>community-driven analysis, network protocol decoding, and in-game behavioral research</strong>.</p>
## Supported Clients
<h1 align="center">
💡 How We Did It
</h1>
<p align="center">
<img src="https://github.com/user-attachments/assets/b6b48cf7-f64a-4497-9750-71f442a3d132" height="300px">
</p>
<p align="center">
<strong>Reverse Engineering</strong>
Every system, packet, opcode, and game mechanic has been reconstructed through countless hours of live packet sniffing, client disassembly, and in-game experimentation by dedicated contributors over the years.
</p>
<p align="center">
No proprietary code or server sources were ever used.
</p>
<p align="center">
All implementations are the result of clean-room engineering.
</p>
<h1 align="center">
🛠️ Technology Stack
</h1>
<p align="center">
<img src="https://github.com/user-attachments/assets/df5ea809-86c5-439d-a8fa-651fb04ba477" style="border-radius: 10px">
</p>
**C++ Core Engine**
* High-performance networking and gameplay logic built in C++
* Cross-platform support for Linux and Windows
**MySQL / MariaDB Backend**
* Fully structured schema with over 200+ tables
* Supports content customization, expansions, and custom worlds
**Scripting Engine**
* Native support for **Perl** and **Lua** scripting
* Powerfully extendable for quests, NPC behaviors, and custom events
**Open Source Content Database**
* Includes ProjectEQs world data up through *Dragons of Norrath*
* 100% customizable to create entirely new game worlds
<h1 align="center">
🚀 Why It Matters
</h1>
<p align="center">🧬 EQEmulator stands as a <strong>technical preservation project</strong>, ensuring that the magic of classic and custom EverQuest servers lives on for future generations of players, tinkerers, and game designers.
</p>
> We humbly acknowledge and thank the original developers at **Verant Interactive** and **Sony Online Entertainment (now Daybreak Game Company)** for creating one of the most influential online experiences in gaming history.
<h1 align="center">
🧑‍💻🖥️ Supported Clients
</h1>
|Titanium Edition|Secrets of Faydwer|Seeds of Destruction|Underfoot|Rain of Fear|
|:---:|:---:|:---:|:---:|:---:|
|<img src="http://i.imgur.com/hrwDxoM.jpg" height="150">|<img src="http://i.imgur.com/cRDW5tn.png" height="150">|<img src="http://i.imgur.com/V48kuVn.jpg" height="150">|<img src="http://i.imgur.com/IJQ0XMa.jpg" height="150">|<img src="http://i.imgur.com/OMpHkKa.png" height="100">|
## Bug Reports <img src="http://i.imgur.com/daf1Vjw.png" height="20">
* Please use the [issue tracker](https://github.com/EQEmu/Server/issues) provided by GitHub to send us bug
reports or feature requests.
* The [EQEmu Forums](http://www.eqemulator.org/forums/) are also a place to submit and get help with bugs.
## 📚 Resources
## Contributions <img src="http://image.flaticon.com/icons/png/512/25/25231.png" width="20">
| Resource | Badges | Link |
|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
| **EQEmulator Docs** | [![Docs](https://img.shields.io/badge/docs-MkDocs%20Powered-blueviolet)](https://docs.eqemu.io) | [docs.eqemu.io](https://docs.eqemu.io/) |
| **Discord Community**| [![Discord](https://img.shields.io/discord/212663220849213441?label=Discord&logo=discord&color=7289DA)](https://discord.gg/QHsm7CD) | [Join Discord](https://discord.gg/QHsm7CD) |
| **Latest Release** | [![Latest Release](https://img.shields.io/github/v/release/eqemu/server)](https://github.com/eqemu/server/releases) <br> [![Release Date](https://img.shields.io/github/release-date/EQEmu/Server)](https://github.com/EQEmu/Server/releases) <br> [![All Releases](https://img.shields.io/github/downloads/eqemu/server/total.svg)](https://github.com/eqemu/server/releases) | [View Releases](https://github.com/eqemu/server/releases) |
| **License** | [![License](https://img.shields.io/github/license/EQEmu/Server)](./LICENSE) | [View License](./LICENSE) |
| **Build Status** | [![Build Status](http://drone.akkadius.com/api/badges/EQEmu/Server/status.svg)](http://drone.akkadius.com/EQEmu/Server) | [View Build Status](http://drone.akkadius.com/EQEmu/Server) |
| **Docker Pulls** | [![Docker Pulls](https://img.shields.io/docker/pulls/akkadius/eqemu-server)](https://hub.docker.com/r/akkadius/eqemu-server) | [Docker Hub](https://hub.docker.com/r/akkadius/eqemu-server) |
| **Contributions** | [![GitHub PRs](https://img.shields.io/github/issues-pr-closed/eqemu/server)](https://github.com/eqemu/server/pulls?q=is%3Apr+is%3Aclosed) | [Closed PRs & Issues](https://github.com/eqemu/server/pulls?q=is%3Apr+is%3Aclosed) |
* The preferred way to contribute is to fork the repo and submit a pull request on
GitHub. If you need help with your changes, you can always post on the forums or
try Discord. You can also post unified diffs (`git diff` should do the trick) on the
[Server Code Submissions](http://www.eqemulator.org/forums/forumdisplay.php?f=669)
forum, although pull requests will be much quicker and easier on all parties.
## 🛠️ Getting Started
## Contact <img src="http://gamerescape.com/wp-content/uploads/2015/06/discord.png" height="20">
If you want to set up your own EQEmulator server, please refer to the current [server installation guides](https://docs.eqemu.io/#server-installation). We've had 100,000s of players and developers use our guides to set up their own servers, and we hope you will too!
- Discord Channel: https://discord.gg/QHsm7CD
- **User Discord Channel**: `#general`
- **Developer Discord Channel**: `#eqemucoders`
## 🗂️ Related Repositories
## Resources
- [EQEmulator Forums](http://www.eqemulator.org/forums)
- [EQEmulator Wiki](https://docs.eqemu.io/)
| Repository | Description |
|--------------------|----------------------------------------------------------------------------------|
| [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests) | Official quests and event scripts for ProjectEQ |
| [Maps](https://github.com/Akkadius/EQEmuMaps) | EQEmu-compatible zone maps |
| [Installer Resources](https://github.com/Akkadius/EQEmuInstall) | Scripts and assets for setting up EQEmu servers |
| [Zone Utilities](https://github.com/EQEmu/zone-utilities) | Utilities for parsing, rendering, and manipulating EQ zone files |
## Related Repositories
* [ProjectEQ Quests](https://github.com/ProjectEQ/projecteqquests)
* [Maps](https://github.com/Akkadius/EQEmuMaps)
* [Installer Resources](https://github.com/Akkadius/EQEmuInstall)
* [Zone Utilities](https://github.com/EQEmu/zone-utilities) - Various utilities and libraries for parsing, rendering and manipulating EQ Zone files.
## Other License Info
* The server code and utilities are released under **GPLv3**
* We also include some small libraries for convienence that may be under different licensing
* SocketLib - GPL LibXML
* zlib - zlib license
* MariaDB/MySQL - GPL
* GPL Perl - GPL / ActiveState (under the assumption that this is a free project)
* CPPUnit - GLP StringUtilities - Apache
* LUA - MIT
## Contributors
+5
View File
@@ -17,6 +17,7 @@ SET(common_sources
database.cpp
database_instances.cpp
database/database_update_manifest.cpp
database/database_update_manifest_custom.cpp
database/database_update_manifest_bots.cpp
database/database_update.cpp
dbcore.cpp
@@ -671,6 +672,7 @@ SET(common_headers
net/console_server_connection.h
net/crc32.h
net/daybreak_connection.h
net/daybreak_pooling.h
net/daybreak_structs.h
net/dns.h
net/endian.h
@@ -682,6 +684,7 @@ SET(common_headers
net/servertalk_server.h
net/servertalk_server_connection.h
net/tcp_connection.h
net/tcp_connection_pooling.h
net/tcp_server.h
net/websocket_server.h
net/websocket_server_connection.h
@@ -742,6 +745,7 @@ SOURCE_GROUP(Net FILES
net/crc32.h
net/daybreak_connection.cpp
net/daybreak_connection.h
net/daybreak_pooling.h
net/daybreak_structs.h
net/dns.h
net/endian.h
@@ -762,6 +766,7 @@ SOURCE_GROUP(Net FILES
net/servertalk_server_connection.h
net/tcp_connection.cpp
net/tcp_connection.h
net/tcp_connection_pooling.h
net/tcp_server.cpp
net/tcp_server.h
net/websocket_server.cpp
+2 -1
View File
@@ -279,7 +279,8 @@ Bazaar::GetSearchResults(
trader_items_ids,
std::string(search.item_name),
field_criteria_items,
where_criteria_items
where_criteria_items,
search.max_results
);
if (item_results.empty()) {
+25 -7
View File
@@ -1095,13 +1095,13 @@ void Database::SetLFP(uint32 character_id, bool is_lfp)
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);
e.firstlogon = first_logon;
e.lfg = is_lfg ? 1 : 0;
e.lfp = is_lfp ? 1 : 0;
e.ingame = ingame;
e.lfg = is_lfg ? 1 : 0;
e.lfp = is_lfp ? 1 : 0;
CharacterDataRepository::UpdateOne(*this, e);
}
@@ -1115,11 +1115,11 @@ void Database::SetLFG(uint32 character_id, bool is_lfg)
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);
e.firstlogon = first_logon;
e.ingame = ingame;
CharacterDataRepository::UpdateOne(*this, e);
}
@@ -1920,6 +1920,7 @@ bool Database::CopyCharacter(
std::vector<std::string> tables_to_zero_id = {
"keyring",
"data_buckets",
"character_evolving_items",
"character_instance_safereturns",
"character_expedition_lockouts",
"character_instance_lockouts",
@@ -1951,6 +1952,12 @@ bool Database::CopyCharacter(
)
);
if (!results.Success()) {
LogError("Transaction failed [{}] rolling back", results.ErrorMessage());
TransactionRollback();
return false;
}
std::vector<std::string> columns = {};
int column_count = 0;
@@ -1969,6 +1976,12 @@ bool Database::CopyCharacter(
)
);
if (!results.Success()) {
LogError("Transaction failed [{}] rolling back", results.ErrorMessage());
TransactionRollback();
return false;
}
std::vector<std::vector<std::string>> new_rows;
for (auto row : results) {
@@ -2036,13 +2049,18 @@ bool Database::CopyCharacter(
LogInfo("Copying table [{}] rows [{}]", table_name, Strings::Commify(rows_copied));
if (!insert.ErrorMessage().empty()) {
LogError("Error copying table [{}] [{}]", table_name, insert.ErrorMessage());
TransactionRollback();
return false;
}
}
}
TransactionCommit();
auto r = TransactionCommit();
if (!r.Success()) {
LogError("Transaction failed [{}] rolling back", r.ErrorMessage());
return false;
}
LogInfo(
"Character [{}] copied to [{}] total rows [{}]",
+2 -1
View File
@@ -141,6 +141,7 @@ public:
bool CheckInstanceExpired(uint16 instance_id);
bool CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version, uint32 duration);
bool GetUnusedInstanceID(uint16& instance_id);
bool TryGetUnusedInstanceID(uint16& instance_id);
bool IsGlobalInstance(uint16 instance_id);
bool RemoveClientFromInstance(uint16 instance_id, uint32 char_id);
bool RemoveClientsFromInstance(uint16 instance_id);
@@ -262,7 +263,7 @@ public:
bool SaveTime(int8 minute, int8 hour, int8 day, int8 month, int16 year);
void ClearMerchantTemp();
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 SetLFP(uint32 character_id, bool is_lfp);
void SetLoginFlags(uint32 character_id, bool is_lfp, bool is_lfg, uint8 first_logon);
+1 -1
View File
@@ -50,7 +50,7 @@ bool DatabaseDumpService::IsMySQLInstalled()
{
std::string version_output = GetMySQLVersion();
return version_output.find("mysql") != std::string::npos && version_output.find("Ver") != std::string::npos;
return version_output.find("mysql") != std::string::npos && (version_output.find("Ver") != std::string::npos || version_output.find("from") != std::string::npos);
}
/**
-1
View File
@@ -65,7 +65,6 @@ private:
bool dump_system_tables = false;
bool dump_content_tables = false;
bool dump_player_tables = false;
bool dump_query_server_tables = false;
bool dump_login_server_tables = false;
bool dump_with_no_data = false;
bool dump_table_lock = false;
+37 -2
View File
@@ -7,6 +7,7 @@
#include "../http/httplib.h"
#include "database_update_manifest.cpp"
#include "database_update_manifest_custom.cpp"
#include "database_update_manifest_bots.cpp"
#include "database_dump_service.h"
@@ -14,7 +15,7 @@ constexpr int BREAK_LENGTH = 70;
DatabaseVersion DatabaseUpdate::GetDatabaseVersions()
{
auto results = m_database->QueryDatabase("SELECT `version`, `bots_version` FROM `db_version` LIMIT 1");
auto results = m_database->QueryDatabase("SELECT `version`, `bots_version`, `custom_version` FROM `db_version` LIMIT 1");
if (!results.Success() || !results.RowCount()) {
LogError("Failed to read from [db_version] table!");
return DatabaseVersion{};
@@ -25,6 +26,7 @@ DatabaseVersion DatabaseUpdate::GetDatabaseVersions()
return DatabaseVersion{
.server_database_version = Strings::ToInt(r[0]),
.bots_database_version = Strings::ToInt(r[1]),
.custom_database_version = Strings::ToInt(r[2]),
};
}
@@ -33,6 +35,7 @@ DatabaseVersion DatabaseUpdate::GetBinaryDatabaseVersions()
return DatabaseVersion{
.server_database_version = CURRENT_BINARY_DATABASE_VERSION,
.bots_database_version = (RuleB(Bots, Enabled) ? CURRENT_BINARY_BOTS_DATABASE_VERSION : 0),
.custom_database_version = CUSTOM_BINARY_DATABASE_VERSION,
};
}
@@ -43,6 +46,7 @@ constexpr int LOOK_BACK_AMOUNT = 10;
// this check will take action
void DatabaseUpdate::CheckDbUpdates()
{
InjectCustomVersionColumn();
InjectBotsVersionColumn();
auto v = GetDatabaseVersions();
auto b = GetBinaryDatabaseVersions();
@@ -59,6 +63,15 @@ void DatabaseUpdate::CheckDbUpdates()
m_database->QueryDatabase(fmt::format("UPDATE `db_version` SET `version` = {}", b.server_database_version));
}
if (UpdateManifest(manifest_entries_custom, v.custom_database_version, b.custom_database_version)) {
LogInfo(
"Updates ran successfully, setting database version to [{}] from [{}]",
b.custom_database_version,
v.custom_database_version
);
m_database->QueryDatabase(fmt::format("UPDATE `db_version` SET `custom_version` = {}", b.custom_database_version));
}
if (b.bots_database_version > 0) {
if (UpdateManifest(bot_manifest_entries, v.bots_database_version, b.bots_database_version)) {
LogInfo(
@@ -344,6 +357,16 @@ bool DatabaseUpdate::CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b)
);
}
if (b.custom_database_version > 0) {
LogInfo(
"{:>8} | database [{}] binary [{}] {}",
"Custom",
v.custom_database_version,
b.custom_database_version,
(v.custom_database_version == b.custom_database_version) ? "up to date" : "checking updates"
);
}
LogInfo("{:>8} | [server.auto_database_updates] [<green>true]", "Config");
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
@@ -353,7 +376,10 @@ bool DatabaseUpdate::CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b)
// bots database version is optional, if not enabled then it is always up-to-date
bool bots_up_to_date = RuleB(Bots, Enabled) ? v.bots_database_version >= b.bots_database_version : true;
return server_up_to_date && bots_up_to_date;
// custom database version is optional, if not enabled then it is always up-to-date
bool custom_up_to_date = v.custom_database_version >= b.custom_database_version;
return server_up_to_date && bots_up_to_date && custom_up_to_date;
}
// checks to see if there are pending updates
@@ -373,3 +399,12 @@ void DatabaseUpdate::InjectBotsVersionColumn()
m_database->QueryDatabase("ALTER TABLE db_version ADD bots_version int(11) DEFAULT '0' AFTER version");
}
}
void DatabaseUpdate::InjectCustomVersionColumn()
{
auto results = m_database->QueryDatabase("SHOW COLUMNS FROM `db_version` LIKE 'custom_version'");
if (!results.Success() || results.RowCount() == 0) {
LogInfo("Adding custom_version column to db_version table");
m_database->QueryDatabase("ALTER TABLE `db_version` ADD COLUMN `custom_version` INT(11) UNSIGNED NOT NULL DEFAULT 0");
}
}
+2
View File
@@ -17,6 +17,7 @@ struct ManifestEntry {
struct DatabaseVersion {
int server_database_version;
int bots_database_version;
int custom_database_version;
};
class DatabaseUpdate {
@@ -38,6 +39,7 @@ private:
Database *m_content_database;
static bool CheckVersionsUpToDate(DatabaseVersion v, DatabaseVersion b);
void InjectBotsVersionColumn();
void InjectCustomVersionColumn();
};
+116 -5
View File
@@ -6792,7 +6792,7 @@ UPDATE `character_corpse_items` SET `equip_slot` = ((`equip_slot` - 341) + 5810)
},
ManifestEntry{
.version = 9304,
.description = "2024_12_01_2024_update_guild_bank",
.description = "2024_12_01_update_guild_bank",
.check = "SHOW COLUMNS FROM `guild_bank` LIKE 'augment_one_id'",
.condition = "empty",
.match = "",
@@ -6914,7 +6914,7 @@ CREATE TABLE `zone_state_spawns` (
},
ManifestEntry{
.version = 9308,
.description = "2025_add_multivalue_support_to_evolving_subtype.sql",
.description = "2025_03_29_add_multivalue_support_to_evolving_subtype.sql",
.check = "SHOW COLUMNS FROM `items_evolving_details` LIKE 'sub_type'",
.condition = "missing",
.match = "varchar(200)",
@@ -6942,8 +6942,8 @@ CREATE TABLE `character_pet_name` (
.version = 9310,
.description = "2025_03_7_expand_horse_def.sql",
.check = "SHOW COLUMNS FROM `horses` LIKE 'helmtexture'",
.condition = "missing",
.match = "TINYINT(2)",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `horses`
ADD COLUMN `helmtexture` TINYINT(2) NOT NULL DEFAULT -1 AFTER `texture`;
@@ -6986,7 +6986,6 @@ ALTER TABLE data_buckets ADD INDEX idx_bot_expires (bot_id, expires);
.match = "idx_zone_instance",
.sql = R"(
ALTER TABLE zone_state_spawns ADD INDEX idx_zone_instance (zone_id, instance_id);
ALTER TABLE zone_state_spawns ADD INDEX idx_instance_id (instance_id);
)",
.content_schema_update = false
},
@@ -6998,6 +6997,118 @@ ALTER TABLE zone_state_spawns ADD INDEX idx_instance_id (instance_id);
.match = "",
.sql = R"(
TRUNCATE TABLE zone_state_spawns;
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9315,
.description = "2025_03_29_character_tribute_index.sql",
.check = "SHOW INDEX FROM character_tribute",
.condition = "missing",
.match = "idx_character_id",
.sql = R"(
ALTER TABLE character_tribute ADD INDEX idx_character_id (character_id);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9316,
.description = "2025_03_29_player_titlesets_index.sql",
.check = "SHOW INDEX FROM player_titlesets",
.condition = "missing",
.match = "idx_char_id",
.sql = R"(
ALTER TABLE player_titlesets ADD INDEX idx_char_id (char_id);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9317,
.description = "2025_03_29_respawn_times_instance_index.sql",
.check = "SHOW INDEX FROM respawn_times",
.condition = "missing",
.match = "idx_instance_id",
.sql = R"(
ALTER TABLE respawn_times ADD INDEX idx_instance_id (instance_id);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9318,
.description = "2025_03_29_zone_state_spawns_instance_index.sql",
.check = "SHOW INDEX FROM zone_state_spawns",
.condition = "missing",
.match = "idx_instance_id",
.sql = R"(
ALTER TABLE zone_state_spawns ADD INDEX idx_instance_id (instance_id);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9319,
.description = "2025_03_29_data_buckets_expires_index.sql",
.check = "SHOW INDEX FROM data_buckets",
.condition = "missing",
.match = "idx_expires",
.sql = R"(
CREATE INDEX idx_expires ON data_buckets (expires);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9320,
.description = "2025_03_23_add_respawn_times_expire_at.sql",
.check = "SHOW COLUMNS FROM `respawn_times` LIKE 'expire_at'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `respawn_times`
ADD COLUMN `expire_at` int(11) UNSIGNED NULL DEFAULT 0 AFTER `duration`;
UPDATE respawn_times set expire_at = `start` + `duration`; -- backfill existing data
CREATE INDEX `idx_expire_at` ON `respawn_times` (`expire_at`);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9321,
.description = "2025_03_30_instance_list_add_expire_at.sql",
.check = "SHOW COLUMNS FROM `instance_list` LIKE 'expire_at'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `instance_list`
ADD COLUMN `expire_at` bigint(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `duration`;
UPDATE instance_list set expire_at = `start_time` + `duration`; -- backfill existing data
CREATE INDEX `idx_expire_at` ON `instance_list` (`expire_at`);
)",
.content_schema_update = false
},
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
},
@@ -0,0 +1,55 @@
#include "database_update.h"
std::vector<ManifestEntry> manifest_entries_custom = {
ManifestEntry{
.version = 1,
.description = "2025_05_16_new_database_check_test",
.check = "SHOW TABLES LIKE 'new_table'",
.condition = "empty",
.match = "",
.sql = R"(
CREATE TABLE `new_table` (
`id` int NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
);
)",
.content_schema_update = false,
},
// Used for testing
// ManifestEntry{
// .version = 9229,
// .description = "new_database_check_test",
// .check = "SHOW TABLES LIKE 'new_table'",
// .condition = "empty",
// .match = "",
// .sql = R"(
//CREATE TABLE `new_table` (
// `id` int NOT NULL AUTO_INCREMENT,
// PRIMARY KEY (`id`)
//);
//CREATE TABLE `new_table1` (
// `id` int NOT NULL AUTO_INCREMENT,
// PRIMARY KEY (`id`)
//);
//CREATE TABLE `new_table2` (
// `id` int NOT NULL AUTO_INCREMENT,
// PRIMARY KEY (`id`)
//);
//CREATE TABLE `new_table3` (
// `id` int NOT NULL AUTO_INCREMENT,
// PRIMARY KEY (`id`)
//);
//)",
// }
};
// see struct definitions for what each field does
// struct ManifestEntry {
// int version{}; // database version of the migration
// std::string description{}; // description of the migration ex: "add_new_table" or "add_index_to_table"
// std::string check{}; // query that checks against the condition
// std::string condition{}; // condition or "match_type" - Possible values [contains|match|missing|empty|not_empty]
// std::string match{}; // match field that is not always used, but works in conjunction with "condition" values [missing|match|contains]
// std::string sql{}; // the SQL DDL that gets ran when the condition is true
// };
+45 -18
View File
@@ -128,11 +128,35 @@ bool Database::CreateInstance(uint16 instance_id, uint32 zone_id, uint32 version
e.version = version;
e.start_time = std::time(nullptr);
e.duration = duration;
e.expire_at = e.start_time + duration;
return InstanceListRepository::InsertOne(*this, e).id;
RespawnTimesRepository::ClearInstanceTimers(*this, e.id);
InstanceListRepository::ReplaceOne(*this, e);
return instance_id > 0 && e.id;
}
bool Database::GetUnusedInstanceID(uint16 &instance_id)
{
// attempt to get an unused instance id
for (int a = 0; a < 10; a++) {
uint16 attempted_id = 0;
if (TryGetUnusedInstanceID(attempted_id)) {
auto i = InstanceListRepository::NewEntity();
i.id = attempted_id;
i.notes = "Prefetching";
auto n = InstanceListRepository::InsertOne(*this, i);
if (n.id > 0) {
instance_id = n.id;
return true;
}
}
}
instance_id = 0;
return false;
}
bool Database::TryGetUnusedInstanceID(uint16 &instance_id)
{
uint32 max_reserved_instance_id = RuleI(Instances, ReservedInstances);
uint32 max_instance_id = 32000;
@@ -537,14 +561,12 @@ void Database::GetCharactersInInstance(uint16 instance_id, std::list<uint32> &ch
void Database::PurgeExpiredInstances()
{
/**
* Delay purging by a day so that we can continue using adjacent free instance id's
* from the table without risking the chance we immediately re-allocate a zone that freshly expired but
* has not been fully de-allocated
*/
auto l = InstanceListRepository::GetWhere(
*this,
"(start_time + duration) <= (UNIX_TIMESTAMP() - 86400) AND never_expires = 0"
fmt::format(
"expire_at <= (UNIX_TIMESTAMP() - {}) and expire_at != 0 AND never_expires = 0",
RuleI(Instances, ExpireOffsetTimeSeconds)
)
);
if (l.empty()) {
return;
@@ -555,20 +577,24 @@ void Database::PurgeExpiredInstances()
instance_ids.emplace_back(std::to_string(e.id));
}
const auto imploded_instance_ids = Strings::Implode(",", instance_ids);
const auto ids = Strings::Implode(",", instance_ids);
InstanceListRepository::DeleteWhere(*this, fmt::format("id IN ({})", imploded_instance_ids));
InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id IN ({})", imploded_instance_ids));
RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
CharacterCorpsesRepository::BuryInstances(*this, imploded_instance_ids);
DynamicZoneMembersRepository::DeleteByManyInstances(*this, imploded_instance_ids);
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
Spawn2DisabledRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", imploded_instance_ids));
DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id != 0 and instance_id IN ({})", imploded_instance_ids));
TransactionBegin();
InstanceListRepository::DeleteWhere(*this, fmt::format("id IN ({})", ids));
InstanceListPlayerRepository::DeleteWhere(*this, fmt::format("id IN ({})", ids));
RespawnTimesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
SpawnConditionValuesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
CharacterCorpsesRepository::BuryInstances(*this, ids);
DynamicZoneMembersRepository::DeleteByManyInstances(*this, ids);
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
Spawn2DisabledRepository::DeleteWhere(*this, fmt::format("instance_id IN ({})", ids));
DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id != 0 and instance_id IN ({})", ids));
if (RuleB(Zone, StateSavingOnShutdown)) {
ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` IN ({})", imploded_instance_ids));
ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` IN ({})", ids));
}
TransactionCommit();
LogInfo("Purged [{}] expired instances", l.size());
}
void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
@@ -580,6 +606,7 @@ void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
i.start_time = std::time(nullptr);
i.duration = new_duration;
i.expire_at = i.start_time + i.duration;
InstanceListRepository::UpdateOne(*this, i);
}
+1 -1
View File
@@ -80,7 +80,7 @@ namespace DatabaseSchema {
{"guild_members", "char_id"},
{"guilds", "id"},
{"instance_list_player", "id"},
{"inventory", "charid"},
{"inventory", "character_id"},
{"inventory_snapshots", "charid"},
{"keyring", "char_id"},
{"mail", "charid"},
+2 -2
View File
@@ -189,9 +189,9 @@ void DBcore::TransactionBegin()
QueryDatabase("START TRANSACTION");
}
void DBcore::TransactionCommit()
MySQLRequestResult DBcore::TransactionCommit()
{
QueryDatabase("COMMIT");
return QueryDatabase("COMMIT");
}
void DBcore::TransactionRollback()
+1 -1
View File
@@ -32,7 +32,7 @@ public:
MySQLRequestResult QueryDatabase(const std::string& query, bool retryOnFailureOnce = true);
MySQLRequestResult QueryDatabaseMulti(const std::string &query);
void TransactionBegin();
void TransactionCommit();
MySQLRequestResult TransactionCommit();
void TransactionRollback();
std::string Escape(const std::string& s);
uint32 DoEscapeString(char *tobuf, const char *frombuf, uint32 fromlen);
+4 -3
View File
@@ -58,15 +58,16 @@ uint32_t DynamicZoneBase::CreateInstance()
insert_instance.start_time = static_cast<int>(std::chrono::system_clock::to_time_t(m_start_time));
insert_instance.duration = static_cast<int>(m_duration.count());
insert_instance.never_expires = m_never_expires;
insert_instance.expire_at = insert_instance.start_time + insert_instance.duration;
auto instance = InstanceListRepository::InsertOne(GetDatabase(), insert_instance);
if (instance.id == 0)
auto instance = InstanceListRepository::ReplaceOne(GetDatabase(), insert_instance);
if (!instance)
{
LogDynamicZones("Failed to create instance [{}] for zone [{}]", unused_instance_id, m_zone_id);
return 0;
}
m_instance_id = instance.id;
m_instance_id = unused_instance_id;
return m_instance_id;
}
+2
View File
@@ -324,6 +324,8 @@ union
bool guild_show;
bool trader;
bool buyer;
bool untargetable;
uint32 npc_tint_id;
};
struct PlayerState_Struct {
+27 -8
View File
@@ -682,14 +682,33 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings(bool silent_load)
if (is_missing_in_database && !is_deprecated_category) {
LogInfo("Automatically adding new log category [{}] ({})", Logs::LogCategoryName[i], i);
auto new_category = LogsysCategoriesRepository::NewEntity();
new_category.log_category_id = i;
new_category.log_category_description = Strings::Escape(Logs::LogCategoryName[i]);
new_category.log_to_console = log_settings[i].log_to_console;
new_category.log_to_gmsay = log_settings[i].log_to_gmsay;
new_category.log_to_file = log_settings[i].log_to_file;
new_category.log_to_discord = log_settings[i].log_to_discord;
db_categories_to_add.emplace_back(new_category);
auto e = LogsysCategoriesRepository::NewEntity();
e.log_category_id = i;
e.log_category_description = Strings::Escape(Logs::LogCategoryName[i]);
e.log_to_console = log_settings[i].log_to_console;
e.log_to_gmsay = log_settings[i].log_to_gmsay;
e.log_to_file = log_settings[i].log_to_file;
e.log_to_discord = log_settings[i].log_to_discord;
db_categories_to_add.emplace_back(e);
}
// look to see if the category name is different in the database
auto it = std::find_if(
categories.begin(),
categories.end(),
[i](const auto &c) { return c.log_category_id == i; }
);
if (it != categories.end()) {
if (it->log_category_description != Logs::LogCategoryName[i]) {
LogInfo(
"Updating log category [{}] ({}) to new name [{}]",
it->log_category_description,
i,
Logs::LogCategoryName[i]
);
it->log_category_description = Logs::LogCategoryName[i];
LogsysCategoriesRepository::ReplaceOne(*m_database, *it);
}
}
}
+13 -9
View File
@@ -74,7 +74,7 @@ namespace Logs {
Spawns,
Spells,
Status, // deprecated
TCPConnection,
TCPConnection, // deprecated
Tasks,
Tradeskills,
Trading,
@@ -150,6 +150,8 @@ namespace Logs {
BotSpellTypeChecks,
NpcHandin,
ZoneState,
NetClient,
NetTCP,
MaxCategoryID /* Don't Remove this */
};
@@ -183,7 +185,7 @@ namespace Logs {
"Spawns",
"Spells",
"Status (Deprecated)",
"TCP Connection",
"TCP Connection (Deprecated)",
"Tasks",
"Tradeskills",
"Trading",
@@ -192,8 +194,8 @@ namespace Logs {
"Web Interface (Deprecated)",
"World Server (Deprecated)",
"Zone Server (Deprecated)",
"QueryErr",
"Query",
"MySQL Error",
"MySQL Query",
"Mercenaries",
"Quest Debug",
"Legacy Packet Logging (Deprecated)",
@@ -209,15 +211,15 @@ namespace Logs {
"Traps",
"NPC Roam Box",
"NPC Scaling",
"MobAppearance",
"Mob Appearance",
"Info",
"Warning",
"Critical (Deprecated)",
"Emergency (Deprecated)",
"Alert (Deprecated)",
"Notice (Deprecated)",
"AI Scan",
"AI Yell",
"AI Scan Close",
"AI Yell For Help",
"AI CastBeneficial",
"AOE Cast",
"Entity Management",
@@ -235,7 +237,7 @@ namespace Logs {
"DialogueWindow",
"HTTP",
"Saylink",
"ChecksumVer",
"Checksum Verification",
"CombatRecord",
"Hate",
"Discord",
@@ -258,7 +260,9 @@ namespace Logs {
"Bot Spell Checks",
"Bot Spell Type Checks",
"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__);\
} while (0)
#define LogStatus(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::Status))\
OutF(LogSys, Logs::General, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogStatusDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::Status))\
OutF(LogSys, Logs::Detail, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogTCPConnection(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::TCPConnection))\
OutF(LogSys, Logs::General, Logs::TCPConnection, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogTCPConnectionDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::TCPConnection))\
OutF(LogSys, Logs::Detail, Logs::TCPConnection, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogTasks(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::Tasks))\
OutF(LogSys, Logs::General, Logs::Tasks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
@@ -924,6 +904,26 @@
OutF(LogSys, Logs::Detail, Logs::ZoneState, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogNetClient(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::NetClient))\
OutF(LogSys, Logs::General, Logs::NetClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogNetClientDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::NetClient))\
OutF(LogSys, Logs::Detail, Logs::NetClient, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogNetTCP(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::NetTCP))\
OutF(LogSys, Logs::General, Logs::NetTCP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogNetTCPDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::NetTCP))\
OutF(LogSys, Logs::Detail, Logs::NetTCP, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\
if (LogSys.IsLogEnabled(debug_level, log_category))\
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
+12 -4
View File
@@ -81,7 +81,7 @@ void PlayerEventLogs::Init()
if (!settings_to_insert.empty()) {
PlayerEventLogSettingsRepository::ReplaceMany(*m_database, settings_to_insert);
}
bool processing_in_world = !RuleB(Logging, PlayerEventsQSProcess) && IsWorld();
bool processing_in_qs = RuleB(Logging, PlayerEventsQSProcess) && IsQueryServ();
@@ -181,9 +181,17 @@ void PlayerEventLogs::ProcessBatchQueue()
// Helper to deserialize event data
auto Deserialize = [](const std::string &data, auto &out) {
std::stringstream ss(data);
cereal::JSONInputArchive ar(ss);
out.serialize(ar);
if (!Strings::IsValidJson(data)) {
return;
}
// cpp exceptions are terrible, don't ever use them
try {
std::stringstream ss(data);
cereal::JSONInputArchive ar(ss);
out.serialize(ar);
}
catch (const std::exception &e) {}
};
// Helper to assign ETL table ID
+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
{
if (!inst) {
return;
}
inst.SetEvolveEquipped(false);
if (inst.IsEvolving() && slot_id <= EQ::invslot::EQUIPMENT_END && slot_id >= EQ::invslot::EQUIPMENT_BEGIN) {
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
{
if (!inst) {
return 0;
}
const auto start_iterator = std::ranges::find_if(
evolving_items_manager.GetEvolvingItemsCache().cbegin(),
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
{
if (!inst) {
return 0;
}
int8 const current_level = inst.GetEvolveLvl();
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 ets{};
if (!inst_in) {
return ets;
}
const auto evolve_items = GetEvolveIDItems(inst_in.GetEvolveLoreID());
uint32 max_transfer_level = 0;
int64 xp = in_xp;
@@ -235,6 +251,9 @@ EvolveTransfer EvolvingItemsManager::DetermineTransferResults(
)
{
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_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)
{
if (!inst) {
return;
}
e.item_id = inst.GetID();
e.item_name = inst.GetItem() ? inst.GetItem()->Name : std::string();
e.level = inst.GetEvolveLvl();
+2 -2
View File
@@ -53,11 +53,11 @@ public:
ItemsEvolvingDetailsRepository::ItemsEvolvingDetails GetEvolveItemDetails(uint64 id);
EvolveTransfer DetermineTransferResults(const EQ::ItemInstance& inst_from, const EQ::ItemInstance& inst_to);
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);
private:
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> evolving_items_cache;
std::map<uint32, ItemsEvolvingDetailsRepository::ItemsEvolvingDetails> m_evolving_items_cache;
Database * m_db;
Database * m_content_db;
};
+16 -8
View File
@@ -906,24 +906,32 @@ bool EQ::ItemInstance::IsSlotAllowed(int16 slot_id) const {
bool EQ::ItemInstance::IsDroppable(bool recurse) const
{
if (!m_item)
if (!m_item) {
return false;
}
/*if (m_ornamentidfile) // not implemented
return false;*/
if (m_attuned)
if (m_attuned) {
return false;
/*if (m_item->FVNoDrop != 0) // not implemented
return false;*/
if (m_item->NoDrop == 0)
}
if (RuleI(World, FVNoDropFlag) == FVNoDropFlagRule::Enabled && m_item->FVNoDrop == 0) {
return true;
}
if (m_item->NoDrop == 0) {
return false;
}
if (recurse) {
for (auto iter : m_contents) {
if (!iter.second)
for (auto iter: m_contents) {
if (!iter.second) {
continue;
}
if (!iter.second->IsDroppable(recurse))
if (!iter.second->IsDroppable(recurse)) {
return false;
}
}
}
+142 -104
View File
@@ -1,12 +1,16 @@
#include "daybreak_connection.h"
#include "../event/event_loop.h"
#include "../event/task.h"
#include "../data_verification.h"
#include "crc32.h"
#include "../eqemu_logsys.h"
#include <zlib.h>
#include <fmt/format.h>
#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()
{
@@ -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);
int rc = uv_udp_bind(&m_socket, (const struct sockaddr *)&recv_addr, UV_UDP_REUSEADDR);
rc = uv_udp_recv_start(&m_socket,
[](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
buf->base = new char[suggested_size];
memset(buf->base, 0, suggested_size);
buf->len = suggested_size;
},
rc = uv_udp_recv_start(
&m_socket,
[](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
if (suggested_size > 65536) {
buf->base = new char[suggested_size];
buf->len = suggested_size;
return;
}
static thread_local char temp_buf[65536];
buf->base = temp_buf;
buf->len = 65536;
},
[](uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned flags) {
DaybreakConnectionManager *c = (DaybreakConnectionManager*)handle->data;
if (nread < 0 || addr == nullptr) {
delete[] buf->base;
return;
}
@@ -70,7 +80,10 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop)
uv_ip4_name((const sockaddr_in*)addr, endpoint, 16);
auto port = ntohs(((const sockaddr_in*)addr)->sin_port);
c->ProcessPacket(endpoint, port, buf->base, nread);
delete[] buf->base;
if (buf->len > 65536) {
delete[] buf->base;
}
});
m_attached = loop;
@@ -310,7 +323,7 @@ EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner
m_last_session_stats = Clock::now();
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
@@ -342,16 +355,16 @@ EQ::Net::DaybreakConnection::~DaybreakConnection()
void EQ::Net::DaybreakConnection::Close()
{
if (m_status == StatusConnected) {
if (m_status != StatusDisconnected && m_status != StatusDisconnecting) {
FlushBuffer();
SendDisconnect();
}
if (m_status != StatusDisconnecting) {
m_close_time = Clock::now();
ChangeStatus(StatusDisconnecting);
}
else {
ChangeStatus(StatusDisconnecting);
}
ChangeStatus(StatusDisconnecting);
}
void EQ::Net::DaybreakConnection::QueuePacket(Packet &p)
@@ -634,7 +647,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
p.PutSerialize(0, reply);
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;
@@ -653,7 +666,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
m_max_packet_size = reply.max_packet_size;
ChangeStatus(StatusConnected);
LogNetcode(
LogNetClient(
"[OP_SessionResponse] Session [{}] refresh with encode key [{}]",
m_connect_code,
HostToNetwork(m_encode_key)
@@ -782,7 +795,7 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p)
SendDisconnect();
}
LogNetcode(
LogNetClient(
"[OP_SessionDisconnect] Session [{}] disconnect with encode key [{}]",
m_connect_code,
HostToNetwork(m_encode_key)
@@ -852,7 +865,7 @@ bool EQ::Net::DaybreakConnection::ValidateCRC(Packet &p)
}
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;
}
@@ -1043,7 +1056,7 @@ void EQ::Net::DaybreakConnection::Decompress(Packet &p, size_t offset, size_t le
return;
}
static uint8_t new_buffer[4096];
static thread_local uint8_t new_buffer[4096];
uint8_t *buffer = (uint8_t*)p.Data() + offset;
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)
{
uint8_t new_buffer[2048] = { 0 };
static thread_local uint8_t new_buffer[2048] = { 0 };
uint8_t *buffer = (uint8_t*)p.Data() + offset;
uint32_t new_length = 0;
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)
{
if (m_status == DbProtocolStatus::StatusDisconnected) {
@@ -1117,34 +1126,57 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
auto &first_packet = s->sent_packets.begin()->second;
auto time_since_first_sent = std::chrono::duration_cast<std::chrono::milliseconds>(now - first_packet.first_sent).count();
if (time_since_first_sent >= m_owner->m_options.resend_timeout) {
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
auto first_sent_ms = std::chrono::duration_cast<std::chrono::milliseconds>(first_packet.first_sent.time_since_epoch()).count();
LogNetClient(
"Closing connection for m_endpoint [{}] m_port [{}] time_since_first_sent [{}] >= m_owner->m_options.resend_timeout [{}] now [{}] first_packet.first_sent [{}]",
m_endpoint,
m_port,
time_since_first_sent,
m_owner->m_options.resend_timeout,
now_ms,
first_sent_ms
);
Close();
return;
}
if (m_last_ack - now > std::chrono::milliseconds(1000)) {
LogNetClient(
"Resetting m_acked_since_last_resend flag for m_endpoint [{}] m_port [{}]",
m_endpoint,
m_port
);
m_acked_since_last_resend = true;
}
// make sure that the first_packet in the list first_sent time is within the resend_delay and now
// if it is not, then we need to resend all packets in the list
if (time_since_first_sent <= first_packet.resend_delay && !m_acked_since_last_resend) {
LogNetcodeDetail(
"Not resending packets for stream [{}] time since first sent [{}] resend delay [{}] m_acked_since_last_resend [{}]",
stream,
LogNetClientDetail(
"Not resending packets for m_endpoint [{}] m_port [{}] packets [{}] time_first_sent [{}] resend_delay [{}] m_acked_since_last_resend [{}]",
m_endpoint,
m_port,
s->sent_packets.size(),
time_since_first_sent,
first_packet.resend_delay,
m_acked_since_last_resend
);
return;
}
if (time_since_first_sent >= m_owner->m_options.resend_timeout) {
Close();
return;
}
}
if (LogSys.IsLogEnabled(Logs::Detail, Logs::Netcode)) {
if (LogSys.IsLogEnabled(Logs::General, Logs::NetClient)) {
size_t total_size = 0;
for (auto &e: s->sent_packets) {
total_size += e.second.packet.Length();
}
LogNetcodeDetail(
"Resending packets for stream [{}] packet count [{}] total packet size [{}] m_acked_since_last_resend [{}]",
stream,
LogNetClientDetail(
"Resending packets for m_endpoint [{}] m_port [{}] packet count [{}] total packet size [{}] m_acked_since_last_resend [{}]",
m_endpoint,
m_port,
s->sent_packets.size(),
total_size,
m_acked_since_last_resend
@@ -1154,10 +1186,13 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
for (auto &e: s->sent_packets) {
if (m_resend_packets_sent >= MAX_CLIENT_RECV_PACKETS_PER_WINDOW ||
m_resend_bytes_sent >= MAX_CLIENT_RECV_BYTES_PER_WINDOW) {
LogNetcodeDetail(
"Stopping resend because we hit thresholds m_resend_packets_sent [{}] max [{}] m_resend_bytes_sent [{}] max [{}]",
LogNetClient(
"Stopping resend because we hit thresholds for m_endpoint [{}] m_port [{}] m_resend_packets_sent [{}] max [{}] in_queue [{}] m_resend_bytes_sent [{}] max [{}]",
m_endpoint,
m_port,
m_resend_packets_sent,
MAX_CLIENT_RECV_PACKETS_PER_WINDOW,
s->sent_packets.size(),
m_resend_bytes_sent,
MAX_CLIENT_RECV_BYTES_PER_WINDOW
);
@@ -1194,11 +1229,11 @@ void EQ::Net::DaybreakConnection::ProcessResend(int stream)
}
m_acked_since_last_resend = false;
m_last_ack = now;
}
void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq)
{
auto now = Clock::now();
auto s = &m_streams[stream];
auto iter = s->sent_packets.begin();
@@ -1214,12 +1249,14 @@ void EQ::Net::DaybreakConnection::Ack(int stream, uint16_t seq)
m_rolling_ping = (m_rolling_ping * 2 + round_time) / 3;
iter = s->sent_packets.erase(iter);
m_acked_since_last_resend = true;
}
else {
++iter;
}
}
m_acked_since_last_resend = true;
m_last_ack = now;
}
void EQ::Net::DaybreakConnection::OutOfOrderAck(int stream, uint16_t seq)
@@ -1237,6 +1274,9 @@ void EQ::Net::DaybreakConnection::OutOfOrderAck(int stream, uint16_t seq)
s->sent_packets.erase(iter);
}
m_acked_since_last_resend = true;
m_last_ack = now;
}
void EQ::Net::DaybreakConnection::UpdateDataBudget(double budget_add)
@@ -1333,99 +1373,97 @@ void EQ::Net::DaybreakConnection::SendKeepAlive()
InternalSend(p);
}
void EQ::Net::DaybreakConnection::InternalSend(Packet &p)
{
void EQ::Net::DaybreakConnection::InternalSend(Packet &p) {
if (m_owner->m_options.outgoing_data_rate > 0.0) {
auto new_budget = m_outgoing_budget - (p.Length() / 1024.0);
if (new_budget <= 0.0) {
m_stats.dropped_datarate_packets++;
return;
}
else {
} else {
m_outgoing_budget = new_budget;
}
}
m_last_send = Clock::now();
auto send_func = [](uv_udp_send_t* req, int status) {
delete[](char*)req->data;
delete req;
};
auto pooled_opt = send_buffer_pool.acquire();
if (!pooled_opt) {
m_stats.dropped_datarate_packets++;
return;
}
auto [send_req, data, ctx] = *pooled_opt;
ctx->pool = &send_buffer_pool; // set pool pointer
sockaddr_in send_addr{};
uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr);
uv_buf_t send_buffers[1];
if (PacketCanBeEncoded(p)) {
m_stats.bytes_before_encode += p.Length();
DynamicPacket out;
out.PutPacket(0, p);
for (int i = 0; i < 2; ++i) {
switch (m_encode_passes[i]) {
case EncodeCompression:
if (out.GetInt8(0) == 0)
Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
else
Compress(out, 1, out.Length() - 1);
break;
case EncodeXOR:
if (out.GetInt8(0) == 0)
Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
else
Encode(out, 1, out.Length() - 1);
break;
default:
break;
for (auto &m_encode_passe: m_encode_passes) {
switch (m_encode_passe) {
case EncodeCompression:
if (out.GetInt8(0) == 0) {
Compress(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
} else {
Compress(out, 1, out.Length() - 1);
}
break;
case EncodeXOR:
if (out.GetInt8(0) == 0) {
Encode(out, DaybreakHeader::size(), out.Length() - DaybreakHeader::size());
} else {
Encode(out, 1, out.Length() - 1);
}
break;
default:
break;
}
}
AppendCRC(out);
uv_udp_send_t *send_req = new uv_udp_send_t;
memset(send_req, 0, sizeof(*send_req));
sockaddr_in send_addr;
uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr);
uv_buf_t send_buffers[1];
char *data = new char[out.Length()];
memcpy(data, out.Data(), out.Length());
send_buffers[0] = uv_buf_init(data, out.Length());
send_req->data = send_buffers[0].base;
m_stats.sent_bytes += out.Length();
m_stats.sent_packets++;
if (m_owner->m_options.simulated_out_packet_loss && m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) {
delete[](char*)send_req->data;
delete send_req;
return;
}
uv_udp_send(send_req, &m_owner->m_socket, send_buffers, 1, (sockaddr*)&send_addr, send_func);
return;
} else {
memcpy(data, p.Data(), p.Length());
send_buffers[0] = uv_buf_init(data, p.Length());
}
m_stats.bytes_before_encode += p.Length();
uv_udp_send_t *send_req = new uv_udp_send_t;
sockaddr_in send_addr;
uv_ip4_addr(m_endpoint.c_str(), m_port, &send_addr);
uv_buf_t send_buffers[1];
char *data = new char[p.Length()];
memcpy(data, p.Data(), p.Length());
send_buffers[0] = uv_buf_init(data, p.Length());
send_req->data = send_buffers[0].base;
m_stats.sent_bytes += p.Length();
m_stats.sent_packets++;
if (m_owner->m_options.simulated_out_packet_loss && m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) {
delete[](char*)send_req->data;
delete send_req;
if (m_owner->m_options.simulated_out_packet_loss &&
m_owner->m_options.simulated_out_packet_loss >= m_owner->m_rand.Int(0, 100)) {
send_buffer_pool.release(ctx);
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)
+2
View File
@@ -3,6 +3,7 @@
#include "../random.h"
#include "packet.h"
#include "daybreak_structs.h"
#include "daybreak_pooling.h"
#include <uv.h>
#include <chrono>
#include <functional>
@@ -185,6 +186,7 @@ namespace EQ
size_t m_resend_packets_sent = 0;
size_t m_resend_bytes_sent = 0;
bool m_acked_since_last_resend = false;
Timestamp m_last_ack;
struct DaybreakSentPacket
{
+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;
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
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;
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->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();
});
@@ -58,15 +58,15 @@ void EQ::Net::ServertalkLegacyClient::Connect()
m_connecting = true;
EQ::Net::TCPConnection::Connect(m_addr, m_port, false, [this](std::shared_ptr<EQ::Net::TCPConnection> connection) {
if (connection == nullptr) {
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;
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->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();
});
@@ -131,7 +131,7 @@ void EQ::Net::ServertalkLegacyClient::ProcessReadBuffer()
}
else {
EQ::Net::StaticPacket p(&m_buffer[current + 4], length);
auto cb = m_message_callbacks.find(opcode);
if (cb != m_message_callbacks.end()) {
cb->second(opcode, p);
+108 -55
View File
@@ -1,5 +1,8 @@
#include "tcp_connection.h"
#include "../event/event_loop.h"
#include <iostream>
WriteReqPool tcp_write_pool;
void on_close_handle(uv_handle_t* handle) {
delete (uv_tcp_t *)handle;
@@ -64,36 +67,37 @@ void EQ::Net::TCPConnection::Connect(const std::string &addr, int port, bool ipv
});
}
void EQ::Net::TCPConnection::Start() {
uv_read_start((uv_stream_t*)m_socket, [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
buf->base = new char[suggested_size];
buf->len = suggested_size;
}, [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
void EQ::Net::TCPConnection::Start()
{
uv_read_start(
(uv_stream_t *) m_socket, [](uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
if (suggested_size > 65536) {
buf->base = new char[suggested_size];
buf->len = suggested_size;
return;
}
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) {
connection->Read(buf->base, nread);
if (nread > 0) {
connection->Read(buf->base, nread);
}
else if (nread == UV_EOF) {
connection->Disconnect();
}
else if (nread < 0) {
connection->Disconnect();
}
if (buf->base) {
delete[] buf->base;
if (buf->len > 65536) {
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)
@@ -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)
{
if (!m_socket) {
void EQ::Net::TCPConnection::Write(const char* data, size_t count) {
if (!m_socket || !data || count == 0) {
std::cerr << "TCPConnection::Write - Invalid socket or data\n";
return;
}
struct WriteBaton
{
TCPConnection *connection;
char *buffer;
};
WriteBaton *baton = new WriteBaton;
baton->connection = this;
baton->buffer = new char[count];
uv_write_t *write_req = new uv_write_t;
memset(write_req, 0, sizeof(uv_write_t));
write_req->data = baton;
uv_buf_t send_buffers[1];
memcpy(baton->buffer, data, count);
send_buffers[0] = uv_buf_init(baton->buffer, count);
uv_write(write_req, (uv_stream_t*)m_socket, send_buffers, 1, [](uv_write_t* req, int status) {
WriteBaton *baton = (WriteBaton*)req->data;
delete[] baton->buffer;
delete req;
if (status < 0) {
baton->connection->Disconnect();
if (count <= TCP_BUFFER_SIZE) {
// Fast path: use pooled request with embedded buffer
auto req_opt = tcp_write_pool.acquire();
if (!req_opt) {
std::cerr << "TCPConnection::Write - Out of write requests\n";
return;
}
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
{
sockaddr_storage addr;
+2 -1
View File
@@ -1,5 +1,6 @@
#pragma once
#include "tcp_connection_pooling.h"
#include <functional>
#include <string>
#include <memory>
@@ -16,7 +17,7 @@ namespace EQ
~TCPConnection();
static void Connect(const std::string &addr, int port, bool ipv6, std::function<void(std::shared_ptr<TCPConnection>)> cb);
void Start();
void OnRead(std::function<void(TCPConnection*, const unsigned char *, size_t)> cb);
void OnDisconnect(std::function<void(TCPConnection*)> cb);
+125
View File
@@ -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->showhelm = emu->showhelm;
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->showname = ShowName;
@@ -4839,13 +4839,13 @@ namespace RoF2
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, 0); // NpcTintIndex
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex
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); // ^
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->npc_tint_id); // NpcTintIndex
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex
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); // ^
if ((emu->NPC == 0) || (emu->race <= Race::Gnome) || (emu->race == Race::Iksar) ||
(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;
}
else if (serverSlot <= EQ::invbag::GENERAL_BAGS_8_END && serverSlot >= EQ::invbag::GENERAL_BAGS_BEGIN) {
UFSlot = serverSlot + 11;
else if (serverSlot <= EQ::invbag::GENERAL_BAGS_END && serverSlot >= EQ::invbag::GENERAL_BAGS_BEGIN) {
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) {
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) {
@@ -4933,7 +4933,7 @@ namespace UF
}
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) {
@@ -4941,7 +4941,7 @@ namespace UF
}
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) {
@@ -4949,7 +4949,7 @@ namespace UF
}
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) {
@@ -4991,11 +4991,11 @@ namespace UF
}
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) {
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) {
@@ -5015,7 +5015,7 @@ namespace UF
}
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) {
@@ -5023,7 +5023,7 @@ namespace UF
}
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) {
@@ -5031,7 +5031,7 @@ namespace UF
}
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) {
@@ -115,7 +115,8 @@ public:
uint8_t lfg;
std::string mailkey;
uint8_t xtargets;
int8_t firstlogon;
uint8_t ingame;
uint32_t first_login;
uint32_t e_aa_effects;
uint32_t e_percent_to_aa;
uint32_t e_expended_aa_spent;
@@ -230,7 +231,8 @@ public:
"lfg",
"mailkey",
"xtargets",
"firstlogon",
"ingame",
"first_login",
"e_aa_effects",
"e_percent_to_aa",
"e_expended_aa_spent",
@@ -341,7 +343,8 @@ public:
"lfg",
"mailkey",
"xtargets",
"firstlogon",
"ingame",
"first_login",
"e_aa_effects",
"e_percent_to_aa",
"e_expended_aa_spent",
@@ -486,7 +489,8 @@ public:
e.lfg = 0;
e.mailkey = "";
e.xtargets = 5;
e.firstlogon = 0;
e.ingame = 0;
e.first_login = 0;
e.e_aa_effects = 0;
e.e_percent_to_aa = 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.mailkey = row[94] ? row[94] : "";
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.e_aa_effects = 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_expended_aa_spent = 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.aa_points_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.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10);
e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
e.ingame = row[96] ? static_cast<uint8_t>(strtoul(row[96], nullptr, 10)) : 0;
e.first_login = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
e.e_aa_effects = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
e.e_percent_to_aa = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
e.e_expended_aa_spent = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
e.aa_points_spent_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
e.aa_points_old = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
e.e_last_invsnapshot = row[103] ? static_cast<uint32_t>(strtoul(row[103], 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;
}
@@ -764,15 +769,16 @@ public:
v.push_back(columns[93] + " = " + std::to_string(e.lfg));
v.push_back(columns[94] + " = '" + Strings::Escape(e.mailkey) + "'");
v.push_back(columns[95] + " = " + std::to_string(e.xtargets));
v.push_back(columns[96] + " = " + std::to_string(e.firstlogon));
v.push_back(columns[97] + " = " + std::to_string(e.e_aa_effects));
v.push_back(columns[98] + " = " + std::to_string(e.e_percent_to_aa));
v.push_back(columns[99] + " = " + std::to_string(e.e_expended_aa_spent));
v.push_back(columns[100] + " = " + std::to_string(e.aa_points_spent_old));
v.push_back(columns[101] + " = " + std::to_string(e.aa_points_old));
v.push_back(columns[102] + " = " + std::to_string(e.e_last_invsnapshot));
v.push_back(columns[103] + " = FROM_UNIXTIME(" + (e.deleted_at > 0 ? std::to_string(e.deleted_at) : "null") + ")");
v.push_back(columns[104] + " = " + std::to_string(e.illusion_block));
v.push_back(columns[96] + " = " + std::to_string(e.ingame));
v.push_back(columns[97] + " = " + std::to_string(e.first_login));
v.push_back(columns[98] + " = " + std::to_string(e.e_aa_effects));
v.push_back(columns[99] + " = " + std::to_string(e.e_percent_to_aa));
v.push_back(columns[100] + " = " + std::to_string(e.e_expended_aa_spent));
v.push_back(columns[101] + " = " + std::to_string(e.aa_points_spent_old));
v.push_back(columns[102] + " = " + std::to_string(e.aa_points_old));
v.push_back(columns[103] + " = " + std::to_string(e.e_last_invsnapshot));
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(
fmt::format(
@@ -890,7 +896,8 @@ public:
v.push_back(std::to_string(e.lfg));
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
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_percent_to_aa));
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("'" + Strings::Escape(e.mailkey) + "'");
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_percent_to_aa));
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.mailkey = row[94] ? row[94] : "";
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.e_aa_effects = 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_expended_aa_spent = 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.aa_points_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.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10);
e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
e.ingame = row[96] ? static_cast<uint8_t>(strtoul(row[96], nullptr, 10)) : 0;
e.first_login = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
e.e_aa_effects = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
e.e_percent_to_aa = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
e.e_expended_aa_spent = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
e.aa_points_spent_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
e.aa_points_old = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
e.e_last_invsnapshot = row[103] ? static_cast<uint32_t>(strtoul(row[103], 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);
}
@@ -1291,15 +1300,16 @@ public:
e.lfg = row[93] ? static_cast<uint8_t>(strtoul(row[93], nullptr, 10)) : 0;
e.mailkey = row[94] ? row[94] : "";
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.e_aa_effects = 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_expended_aa_spent = 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.aa_points_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.deleted_at = strtoll(row[103] ? row[103] : "-1", nullptr, 10);
e.illusion_block = row[104] ? static_cast<uint8_t>(strtoul(row[104], nullptr, 10)) : 0;
e.ingame = row[96] ? static_cast<uint8_t>(strtoul(row[96], nullptr, 10)) : 0;
e.first_login = row[97] ? static_cast<uint32_t>(strtoul(row[97], nullptr, 10)) : 0;
e.e_aa_effects = row[98] ? static_cast<uint32_t>(strtoul(row[98], nullptr, 10)) : 0;
e.e_percent_to_aa = row[99] ? static_cast<uint32_t>(strtoul(row[99], nullptr, 10)) : 0;
e.e_expended_aa_spent = row[100] ? static_cast<uint32_t>(strtoul(row[100], nullptr, 10)) : 0;
e.aa_points_spent_old = row[101] ? static_cast<uint32_t>(strtoul(row[101], nullptr, 10)) : 0;
e.aa_points_old = row[102] ? static_cast<uint32_t>(strtoul(row[102], nullptr, 10)) : 0;
e.e_last_invsnapshot = row[103] ? static_cast<uint32_t>(strtoul(row[103], 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);
}
@@ -1470,7 +1480,8 @@ public:
v.push_back(std::to_string(e.lfg));
v.push_back("'" + Strings::Escape(e.mailkey) + "'");
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_percent_to_aa));
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("'" + Strings::Escape(e.mailkey) + "'");
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_percent_to_aa));
v.push_back(std::to_string(e.e_expended_aa_spent));
@@ -25,6 +25,7 @@ public:
uint8_t is_global;
uint32_t start_time;
uint32_t duration;
uint64_t expire_at;
uint8_t never_expires;
std::string notes;
};
@@ -43,6 +44,7 @@ public:
"is_global",
"start_time",
"duration",
"expire_at",
"never_expires",
"notes",
};
@@ -57,6 +59,7 @@ public:
"is_global",
"start_time",
"duration",
"expire_at",
"never_expires",
"notes",
};
@@ -105,6 +108,7 @@ public:
e.is_global = 0;
e.start_time = 0;
e.duration = 0;
e.expire_at = 0;
e.never_expires = 0;
e.notes = "";
@@ -149,8 +153,9 @@ public:
e.is_global = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
e.start_time = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
e.notes = row[7] ? row[7] : "";
e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
e.never_expires = row[7] ? static_cast<uint8_t>(strtoul(row[7], nullptr, 10)) : 0;
e.notes = row[8] ? row[8] : "";
return e;
}
@@ -189,8 +194,9 @@ public:
v.push_back(columns[3] + " = " + std::to_string(e.is_global));
v.push_back(columns[4] + " = " + std::to_string(e.start_time));
v.push_back(columns[5] + " = " + std::to_string(e.duration));
v.push_back(columns[6] + " = " + std::to_string(e.never_expires));
v.push_back(columns[7] + " = '" + Strings::Escape(e.notes) + "'");
v.push_back(columns[6] + " = " + std::to_string(e.expire_at));
v.push_back(columns[7] + " = " + std::to_string(e.never_expires));
v.push_back(columns[8] + " = '" + Strings::Escape(e.notes) + "'");
auto results = db.QueryDatabase(
fmt::format(
@@ -218,6 +224,7 @@ public:
v.push_back(std::to_string(e.is_global));
v.push_back(std::to_string(e.start_time));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.never_expires));
v.push_back("'" + Strings::Escape(e.notes) + "'");
@@ -255,6 +262,7 @@ public:
v.push_back(std::to_string(e.is_global));
v.push_back(std::to_string(e.start_time));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.never_expires));
v.push_back("'" + Strings::Escape(e.notes) + "'");
@@ -296,8 +304,9 @@ public:
e.is_global = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
e.start_time = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
e.notes = row[7] ? row[7] : "";
e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
e.never_expires = row[7] ? static_cast<uint8_t>(strtoul(row[7], nullptr, 10)) : 0;
e.notes = row[8] ? row[8] : "";
all_entries.push_back(e);
}
@@ -328,8 +337,9 @@ public:
e.is_global = row[3] ? static_cast<uint8_t>(strtoul(row[3], nullptr, 10)) : 0;
e.start_time = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.duration = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.never_expires = row[6] ? static_cast<uint8_t>(strtoul(row[6], nullptr, 10)) : 0;
e.notes = row[7] ? row[7] : "";
e.expire_at = row[6] ? strtoull(row[6], nullptr, 10) : 0;
e.never_expires = row[7] ? static_cast<uint8_t>(strtoul(row[7], nullptr, 10)) : 0;
e.notes = row[8] ? row[8] : "";
all_entries.push_back(e);
}
@@ -410,6 +420,7 @@ public:
v.push_back(std::to_string(e.is_global));
v.push_back(std::to_string(e.start_time));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.never_expires));
v.push_back("'" + Strings::Escape(e.notes) + "'");
@@ -440,6 +451,7 @@ public:
v.push_back(std::to_string(e.is_global));
v.push_back(std::to_string(e.start_time));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.never_expires));
v.push_back("'" + Strings::Escape(e.notes) + "'");
@@ -149,6 +149,7 @@ public:
uint8_t keeps_sold_items;
uint8_t is_parcel_merchant;
uint8_t multiquest_enabled;
uint16_t npc_tint_id;
};
static std::string PrimaryKey()
@@ -289,6 +290,7 @@ public:
"keeps_sold_items",
"is_parcel_merchant",
"multiquest_enabled",
"npc_tint_id",
};
}
@@ -425,6 +427,7 @@ public:
"keeps_sold_items",
"is_parcel_merchant",
"multiquest_enabled",
"npc_tint_id",
};
}
@@ -595,6 +598,7 @@ public:
e.keeps_sold_items = 1;
e.is_parcel_merchant = 0;
e.multiquest_enabled = 0;
e.npc_tint_id = 0;
return e;
}
@@ -761,6 +765,7 @@ public:
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.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;
}
@@ -923,6 +928,7 @@ public:
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[129] + " = " + std::to_string(e.multiquest_enabled));
v.push_back(columns[130] + " = " + std::to_string(e.npc_tint_id));
auto results = db.QueryDatabase(
fmt::format(
@@ -1074,6 +1080,7 @@ public:
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.multiquest_enabled));
v.push_back(std::to_string(e.npc_tint_id));
auto results = db.QueryDatabase(
fmt::format(
@@ -1233,6 +1240,7 @@ public:
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.multiquest_enabled));
v.push_back(std::to_string(e.npc_tint_id));
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.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.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
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.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.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
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.is_parcel_merchant));
v.push_back(std::to_string(e.multiquest_enabled));
v.push_back(std::to_string(e.npc_tint_id));
auto results = db.QueryDatabase(
fmt::format(
@@ -1906,6 +1917,7 @@ public:
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.multiquest_enabled));
v.push_back(std::to_string(e.npc_tint_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@@ -19,10 +19,11 @@
class BaseRespawnTimesRepository {
public:
struct RespawnTimes {
int32_t id;
int32_t start;
int32_t duration;
int16_t instance_id;
int32_t id;
int32_t start;
int32_t duration;
uint32_t expire_at;
int16_t instance_id;
};
static std::string PrimaryKey()
@@ -36,6 +37,7 @@ public:
"id",
"start",
"duration",
"expire_at",
"instance_id",
};
}
@@ -46,6 +48,7 @@ public:
"id",
"start",
"duration",
"expire_at",
"instance_id",
};
}
@@ -90,6 +93,7 @@ public:
e.id = 0;
e.start = 0;
e.duration = 0;
e.expire_at = 0;
e.instance_id = 0;
return e;
@@ -130,7 +134,8 @@ public:
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
e.expire_at = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.instance_id = row[4] ? static_cast<int16_t>(atoi(row[4])) : 0;
return e;
}
@@ -167,7 +172,8 @@ public:
v.push_back(columns[0] + " = " + std::to_string(e.id));
v.push_back(columns[1] + " = " + std::to_string(e.start));
v.push_back(columns[2] + " = " + std::to_string(e.duration));
v.push_back(columns[3] + " = " + std::to_string(e.instance_id));
v.push_back(columns[3] + " = " + std::to_string(e.expire_at));
v.push_back(columns[4] + " = " + std::to_string(e.instance_id));
auto results = db.QueryDatabase(
fmt::format(
@@ -192,6 +198,7 @@ public:
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.start));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.instance_id));
auto results = db.QueryDatabase(
@@ -225,6 +232,7 @@ public:
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.start));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.instance_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
@@ -262,7 +270,8 @@ public:
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
e.expire_at = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.instance_id = row[4] ? static_cast<int16_t>(atoi(row[4])) : 0;
all_entries.push_back(e);
}
@@ -290,7 +299,8 @@ public:
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.start = row[1] ? static_cast<int32_t>(atoi(row[1])) : 0;
e.duration = row[2] ? static_cast<int32_t>(atoi(row[2])) : 0;
e.instance_id = row[3] ? static_cast<int16_t>(atoi(row[3])) : 0;
e.expire_at = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.instance_id = row[4] ? static_cast<int16_t>(atoi(row[4])) : 0;
all_entries.push_back(e);
}
@@ -368,6 +378,7 @@ public:
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.start));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.instance_id));
auto results = db.QueryDatabase(
@@ -394,6 +405,7 @@ public:
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.start));
v.push_back(std::to_string(e.duration));
v.push_back(std::to_string(e.expire_at));
v.push_back(std::to_string(e.instance_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
+51 -14
View File
@@ -106,13 +106,8 @@ public:
return false;
}
auto buy_lines = BaseBuyerBuyLinesRepository::GetWhere(
db,
fmt::format("`buyer_id` = '{}'", buyer.front().id)
);
if (buy_lines.empty()) {
return false;
}
auto buy_lines =
BaseBuyerBuyLinesRepository::GetWhere(db, fmt::format("`buyer_id` = {}", buyer.front().id));
std::vector<std::string> buy_line_ids{};
for (auto const &bl: buy_lines) {
@@ -121,23 +116,65 @@ public:
DeleteWhere(db, fmt::format("`char_id` = '{}';", char_id));
if (buy_line_ids.empty()) {
return false;
return true;
}
BaseBuyerBuyLinesRepository::DeleteWhere(
db,
fmt::format("`id` IN({})", Strings::Implode(", ", buy_line_ids))
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))
db, fmt::format("`buyer_buy_lines_id` IN({})", Strings::Implode(", ", buy_line_ids))
);
}
return true;
}
static bool DeleteBuyers(Database &db, uint32 char_zone_id, uint32 char_zone_instance_id)
{
auto buyers = GetWhere(
db,
fmt::format(
"`char_zone_id` = {} AND `char_zone_instance_id` = {}", char_zone_id, char_zone_instance_id
)
);
if (buyers.empty()) {
return false;
}
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
+2 -40
View File
@@ -7,49 +7,11 @@
class InstanceListRepository: public BaseInstanceListRepository {
public:
/**
* This file was auto generated and can be modified and extended upon
*
* Base repository methods are automatically
* generated in the "base" version of this repository. The base repository
* is immutable and to be left untouched, while methods in this class
* are used as extension methods for more specific persistence-layer
* accessors or mutators.
*
* Base Methods (Subject to be expanded upon in time)
*
* Note: Not all tables are designed appropriately to fit functionality with all base methods
*
* InsertOne
* UpdateOne
* DeleteOne
* FindOne
* GetWhere(std::string where_filter)
* DeleteWhere(std::string where_filter)
* InsertMany
* All
*
* Example custom methods in a repository
*
* InstanceListRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* InstanceListRepository::GetWhereNeverExpires()
* InstanceListRepository::GetWhereXAndY()
* InstanceListRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
* method that can be re-used easily elsewhere especially if it can use a base repository
* method and encapsulate filters there
*/
// Custom extended repository methods here
static int UpdateDuration(Database& db, uint16 instance_id, uint32_t new_duration)
{
auto results = db.QueryDatabase(
fmt::format(
"UPDATE `{}` SET `duration` = {} WHERE `{}` = {}",
"UPDATE `{}` SET `duration` = {}, `expire_at` = (`duration` + `start_time`) WHERE `{}` = {}",
TableName(),
new_duration,
PrimaryKey(),
@@ -65,7 +27,7 @@ public:
auto results = db.QueryDatabase(
fmt::format(
SQL(
SELECT ((start_time + duration) - UNIX_TIMESTAMP()) AS `remaining` FROM `{}`
SELECT (`expire_at` - UNIX_TIMESTAMP()) AS `remaining` FROM `{}`
WHERE `id` = {}
),
TableName(),
+6 -37
View File
@@ -8,47 +8,11 @@
class RespawnTimesRepository: public BaseRespawnTimesRepository {
public:
/**
* This file was auto generated and can be modified and extended upon
*
* Base repository methods are automatically
* generated in the "base" version of this repository. The base repository
* is immutable and to be left untouched, while methods in this class
* are used as extension methods for more specific persistence-layer
* accessors or mutators.
*
* Base Methods (Subject to be expanded upon in time)
*
* Note: Not all tables are designed appropriately to fit functionality with all base methods
*
* InsertOne
* UpdateOne
* DeleteOne
* FindOne
* GetWhere(std::string where_filter)
* DeleteWhere(std::string where_filter)
* InsertMany
* All
*
* Example custom methods in a repository
*
* RespawnTimesRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* RespawnTimesRepository::GetWhereNeverExpires()
* RespawnTimesRepository::GetWhereXAndY()
* RespawnTimesRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
* method that can be re-used easily elsewhere especially if it can use a base repository
* method and encapsulate filters there
*/
// Custom extended repository methods here
static void ClearExpiredRespawnTimers(Database& db)
{
db.QueryDatabase(
fmt::format(
"DELETE FROM `{}` WHERE (`start` + `duration`) < UNIX_TIMESTAMP(NOW())",
"DELETE FROM `{}` WHERE `expire_at` < UNIX_TIMESTAMP(NOW())",
TableName()
)
);
@@ -77,6 +41,11 @@ public:
return ((r.start + r.duration) - time_seconds);
}
static void ClearInstanceTimers(Database &db, int32_t id)
{
RespawnTimesRepository::DeleteWhere(db, fmt::format("`instance_id` = {}", id));
}
};
#endif //EQEMU_RESPAWN_TIMES_REPOSITORY_H
+23 -10
View File
@@ -54,17 +54,30 @@ public:
{
BulkTraders_Struct all_entries{};
std::vector<DistinctTraders_Struct> distinct_traders;
MySQLRequestResult results;
auto results = db.QueryDatabase(fmt::format(
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"FROM trader AS t "
"JOIN character_data AS c ON t.char_id = c.id "
"WHERE t.char_zone_instance_id = {} "
"ORDER BY t.char_zone_instance_id ASC "
"LIMIT {}",
char_zone_instance_id,
max_results)
);
if (RuleB(Bazaar, UseAlternateBazaarSearch)) {
results = db.QueryDatabase(fmt::format(
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"FROM trader AS t "
"JOIN character_data AS c ON t.char_id = c.id "
"WHERE t.char_zone_instance_id = {} "
"ORDER BY t.char_zone_instance_id ASC "
"LIMIT {}",
char_zone_instance_id,
max_results)
);
}
else {
results = db.QueryDatabase(fmt::format(
"SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name "
"FROM trader AS t "
"JOIN character_data AS c ON t.char_id = c.id "
"ORDER BY t.char_zone_instance_id ASC "
"LIMIT {}",
max_results)
);
}
distinct_traders.reserve(results.RowCount());
+13
View File
@@ -502,6 +502,19 @@ bool RuleManager::UpdateInjectedRules(Database *db, const std::string &rule_set_
}
}
// update rules in the database where the description is different
for (auto &e : RuleValuesRepository::All(*db)) {
auto i = rule_data.find(e.rule_name);
if (i != rule_data.end()) {
// if notes are different, update them
if (i->second.second != nullptr && *i->second.second != e.notes) {
LogInfo("Updating rule [{}] notes to [{}]", i->first, *i->second.second);
e.notes = *i->second.second;
RuleValuesRepository::ReplaceOne(*db, e);
}
}
}
if (injected_rule_entries.size()) {
if (!RuleValuesRepository::InjectRules(*db, injected_rule_entries)) {
return false;
+17 -15
View File
@@ -54,7 +54,7 @@ RULE_INT(Character, CorpseDecayTime, 604800000, "Time after which the corpse dec
RULE_INT(Character, EmptyCorpseDecayTime, 10800000, "Time after which an empty corpse decays (milliseconds) DEFAULT: 10800000 (3 Hours)")
RULE_INT(Character, CorpseResTime, 10800000, "Time after which the corpse can no longer be resurrected (milliseconds) DEFAULT: 10800000 (3 Hours)")
RULE_INT(Character, DuelCorpseResTime, 600000, "Time before cant res corpse after a duel (milliseconds) DEFAULT: 600000 (10 Minutes)")
RULE_INT(Character, CorpseOwnerOnlineTime, 30000, "How often corpse will check if its owner is online DEFAULT: 30000 (30 Seconds)")
RULE_INT(Character, CorpseOwnerOnlineCheckTime, 300, "How often corpse will check if its owner is online DEFAULT: 300 (5 minutes)")
RULE_BOOL(Character, LeaveCorpses, true, "Setting whether you leave a corpse behind")
RULE_BOOL(Character, LeaveNakedCorpses, false, "Setting whether you leave a corpse without items")
RULE_INT(Character, MaxDraggedCorpses, 2, "Maximum number of corpses you can drag at once")
@@ -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, 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, 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_INT(Character, IksarCommonTongue, 95, "Starting value for Common Tongue for Iksars")
RULE_INT(Character, OgreCommonTongue, 95, "Starting value for Common Tongue for Ogres")
@@ -261,6 +262,7 @@ RULE_INT(Guild, TributeTime, 600000, "Time in ms for guild tributes. Default is
RULE_INT(Guild, TributeTimeRefreshInterval, 180000, "Time in ms to send all guild members a Tribute Time refresh. Default is 3 mins.")
RULE_INT(Guild, TributePlatConversionRate, 10, "The conversion rate of platinum donations. Default is 10 guild favor to 1 platinum.")
RULE_BOOL(Guild, UseCharacterMaxLevelForGuildTributes, true, "Guild Tributes will adhere to Character:MaxLevel. Default is true.")
RULE_BOOL(Guild, EnableLFGuild, false, "Enable the LFGuild system (Requires queryserv)")
RULE_CATEGORY_END()
RULE_CATEGORY(Skills)
@@ -290,6 +292,7 @@ RULE_BOOL(Pets, ClientPetsUseOwnerNameInLastName, true, "Disable this to keep cl
RULE_BOOL(Pets, CanTakeNoDrop, false, "Setting whether anyone can give no-drop items to pets")
RULE_INT(Pets, PetTauntRange, 150, "Range at which a pet will taunt targets.")
RULE_BOOL(Pets, AlwaysAllowPetRename, false, "Enable this option to allow /changepetname to work without enabling a pet name change via scripts.")
RULE_BOOL(Pets, PetsRequireLoS, false, "Whether or not pets require line of sight to be told to attack their target")
RULE_CATEGORY_END()
RULE_CATEGORY(GM)
@@ -345,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, CustomFilesUrl, "github.com/knervous/eqnexus/releases", "URL to display at character select if client is missing custom files")
RULE_INT(World, CustomFilesAdminLevel, 20, "Admin level at which custom file key is not required when CustomFilesKey is specified")
RULE_BOOL(World, RealTimeCalculateGuilds, false, "(Temp feature flag) If true, guilds will be calculated in real time instead of at zone boot. This is a performance hit but allows for more dynamic guilds.")
RULE_CATEGORY_END()
RULE_CATEGORY(Zone)
@@ -379,6 +383,7 @@ RULE_BOOL(Zone, StateSaveEntityVariables, true, "Set to true if you want buffs t
RULE_BOOL(Zone, StateSaveBuffs, true, "Set to true if you want buffs to be saved on shutdown")
RULE_INT(Zone, StateSaveClearDays, 7, "Clears state save data older than this many days")
RULE_BOOL(Zone, StateSavingOnShutdown, true, "Set to true if you want zones to save state on shutdown (npcs, corpses, loot, entity variables, buffs etc.)")
RULE_INT(Zone, UpdateWhoTimer, 120, "Seconds between updates to /who list, CLE stale timer")
RULE_CATEGORY_END()
RULE_CATEGORY(Map)
@@ -388,9 +393,10 @@ RULE_BOOL(Map, MobZVisualDebug, false, "Displays spell effects determining wheth
RULE_BOOL(Map, MobPathingVisualDebug, false, "Displays nodes in pathing points in realtime to help with visual debugging")
RULE_REAL(Map, FixPathingZMaxDeltaSendTo, 20, "At runtime in SendTo: maximum change in Z to allow the BestZ code to apply")
RULE_INT(Map, FindBestZHeightAdjust, 1, "Adds this to the current Z before seeking the best Z position")
RULE_BOOL(Map, CheckForLoSCheat, false, "Runs predefined zone checks to check for LoS cheating through doors and such.")
RULE_BOOL(Map, EnableLoSCheatExemptions, false, "Enables exemptions for the LoS Cheat check.")
RULE_REAL(Map, RangeCheckForLoSCheat, 20.0, "Default 20.0. Range to check if one is within range of a door.")
RULE_BOOL(Map, CheckForDoorLoSCheat, true, "Runs LoS checks to prevent cheating through doors.")
RULE_BOOL(Map, EnableLoSCheatExemptions, false, "Enables exemptions for the LoS Cheat check. Must modify source to create these.")
RULE_REAL(Map, RangeCheckForDoorLoSCheat, 250.0, "Default 250.0. Range to check if a door is blocking LoS from the target.")
RULE_STRING(Map, ZonesToCheckDoorCheat, "89,103", "Zones that will check for the door LoS cheat. You can leave it blank to disable, 'all' to check all zones or use a comma-delimited list of zones. Default Sebilis & Chardok")
RULE_CATEGORY_END()
RULE_CATEGORY(Pathing)
@@ -814,6 +820,7 @@ RULE_INT(Bots, PercentChanceToCastDispel, 75, "The chance for a bot to attempt t
RULE_INT(Bots, PercentChanceToCastInCombatBuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
RULE_INT(Bots, PercentChanceToCastHateLine, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
RULE_INT(Bots, PercentChanceToCastMez, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
RULE_INT(Bots, PercentChanceToCastAEMez, 40, "The chance for a bot to attempt to cast the given spell type in combat. Default 40%.")
RULE_INT(Bots, PercentChanceToCastSlow, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
RULE_INT(Bots, PercentChanceToCastDebuff, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
RULE_INT(Bots, PercentChanceToCastCure, 75, "The chance for a bot to attempt to cast the given spell type in combat. Default 75%.")
@@ -825,8 +832,6 @@ RULE_INT(Bots, MinDelayBetweenInCombatCastAttempts, 500, "The minimum delay in m
RULE_INT(Bots, MaxDelayBetweenInCombatCastAttempts, 2000, "The maximum delay in milliseconds between cast attempts while in-combat. This is rolled between the min and max. Default 2500ms.")
RULE_INT(Bots, MinDelayBetweenOutCombatCastAttempts, 1000, "The minimum delay in milliseconds between cast attempts while out-of-combat. This is rolled between the min and max. Default 1000ms.")
RULE_INT(Bots, MaxDelayBetweenOutCombatCastAttempts, 2500, "The maximum delay in milliseconds between cast attempts while out-of-combat. This is rolled between the min and max. Default 2500ms.")
RULE_INT(Bots, MezChance, 60, "60 Default. Chance for a bot to attempt to Mez a target after validating it is eligible.")
RULE_INT(Bots, AEMezChance, 35, "35 Default. Chance for a bot to attempt to AE Mez targets after validating they are eligible.")
RULE_INT(Bots, MezSuccessDelay, 2500, "2500 (2.5 sec) Default. Delay between successful Mez attempts.")
RULE_INT(Bots, AEMezSuccessDelay, 5000, "5000 (5 sec) Default. Delay between successful AEMez attempts.")
RULE_INT(Bots, MezFailDelay, 1250, "1250 (1.25 sec) Default. Delay between failed Mez attempts.")
@@ -843,7 +848,7 @@ RULE_BOOL(Bots, AllowCastAAs, true, "If enabled, players can use ^cast aa to cas
RULE_BOOL(Bots, AllowMagicianEpicPet, false, "If enabled, magician bots can summon their epic pets following the rules AllowMagicianEpicPetLevel")
RULE_INT(Bots, AllowMagicianEpicPetLevel, 50, "If AllowMagicianEpicPet is enabled, bots can start using their epic pets at this level")
RULE_INT(Bots, RequiredMagicianEpicPetItemID, 28034, "If AllowMagicianEpicPet is enabled and this is set, bots will be required to have this item ID equipped to cast their epic. Takes in to account AllowMagicianEpicPetLevel as well. Set to 0 to disable requirement")
RULE_STRING(Bots, EpicPetSpellName, "", "'teleport_zone' in the spell to be cast for epic pets. This must be in their spell list to cast.")
RULE_STRING(Bots, EpicPetSpellName, "", "'teleport_zone' in the spell to be cast for epic pets. This must be in their usable spell list to cast. Empty uses Manifest Elements - 'SumMageMultiElement'")
RULE_INT(Bots, ReclaimEnergySpellID, 331, "Spell ID for reclaim energy when using ^petsettype. Default 331")
RULE_BOOL(Bots, UseSpellPulling, true, "If enabled bots will use a spell to pull when within range. Uses PullSpellID.")
RULE_INT(Bots, PullSpellID, 5225, "Default 5225 - Throw Stone. Spell that will be cast to pull by bots")
@@ -854,15 +859,7 @@ RULE_BOOL(Bots, BotArcheryConsumesAmmo, true, "Set to false to disable Archery A
RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption")
RULE_INT(Bots, StackSizeMin, 20, "20 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).")
RULE_INT(Bots, HasOrMayGetAggroThreshold, 90, "90 Default. Percent threshold of total hate where bots will stop casting spells that generate hate if they are set to try to not pull aggro via spells.")
RULE_REAL(Bots, LowerMeleeDistanceMultiplier, 0.35, "Closest % of the hit box a melee bot will get to the target. Default 0.35")
RULE_REAL(Bots, LowerTauntingMeleeDistanceMultiplier, 0.25, "Closest % of the hit box a taunting melee bot will get to the target. Default 0.25")
RULE_REAL(Bots, LowerMaxMeleeRangeDistanceMultiplier, 0.80, "Closest % of the hit box a max melee range melee bot will get to the target. Default 0.80")
RULE_REAL(Bots, UpperMeleeDistanceMultiplier, 0.55, "Furthest % of the hit box a melee bot will get from the target. Default 0.55")
RULE_REAL(Bots, UpperTauntingMeleeDistanceMultiplier, 0.45, "Furthest % of the hit box a taunting melee bot will get from the target. Default 0.45")
RULE_REAL(Bots, UpperMaxMeleeRangeDistanceMultiplier, 0.95, "Furthest % of the hit box a max melee range melee bot will get from the target. Default 0.95")
RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "If true, when bots are at max melee distance, special abilities including taunt will be disabled. Default True.")
RULE_BOOL(Bots, TauntingBotsFollowTopHate, true, "True Default. If true, bots that are taunting will attempt to stick with whoever currently is top hate.")
RULE_INT(Bots, DistanceTauntingBotsStickMainHate, 10, "If TauntingBotsFollowTopHate is enabled, this is the distance bots will try to stick to whoever currently is Top Hate.")
RULE_INT(Bots, MinJitterTimer, 500, "Minimum ms between bot movement jitter checks.")
RULE_INT(Bots, MaxJitterTimer, 2500, "Maximum ms between bot movement jitter checks. Set to 0 to disable timer checks.")
RULE_BOOL(Bots, PreventBotCampOnFD, true, "True Default. If true, players will not be able to camp bots while feign death.")
@@ -882,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, 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, 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, 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.")
@@ -903,6 +901,7 @@ RULE_STRING(Bots, ZonesWithForcedSpawnLimits, "", "Comma-delimited list of zones
RULE_STRING(Bots, ZoneForcedSpawnLimits, "", "Comma-delimited list of forced spawn limits for zones.")
RULE_INT(Bots, AICastSpellTypeDelay, 100, "Delay in milliseconds between AI cast attempts for each spell type. Default 100ms")
RULE_INT(Bots, AICastSpellTypeHeldDelay, 2500, "Delay in milliseconds between AI cast attempts for each spell type that is held or disabled. Default 2500ms (2.5s)")
RULE_BOOL(Bots, BotsRequireLoS, true, "Whether or not bots require line of sight to be told to attack their target")
RULE_CATEGORY_END()
RULE_CATEGORY(Chat)
@@ -926,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, QuestDialogueUsesDialogueWindow, false, "Pipes all quest dialogue to dialogue window")
RULE_BOOL(Chat, DialogueWindowAnimatesNPCsIfNoneSet, true, "If there is no animation specified in the dialogue window markdown then it will choose a random greet animation such as wave or salute")
RULE_BOOL(Chat, AlwaysCaptureCommandText, false, "Consume command text (# and ^ by default), regardless of which channel it is sent to")
RULE_CATEGORY_END()
RULE_CATEGORY(Merchant)
@@ -1073,6 +1073,7 @@ RULE_CATEGORY(Logging)
RULE_BOOL(Logging, PrintFileFunctionAndLine, false, "Ex: [World Server] [net.cpp::main:309] Loading variables...")
RULE_BOOL(Logging, WorldGMSayLogging, true, "Relay worldserver logging to zone processes via GM say output")
RULE_BOOL(Logging, PlayerEventsQSProcess, false, "Have query server process player events instead of world. Useful when wanting to use a dedicated server and database for processing player events on separate disk")
RULE_STRING(Logging, PlayerEventsIgnoreGMCommands, "help,show", "This is a comma delimited list of commands to ignore when recording GM command player events.")
RULE_INT(Logging, BatchPlayerEventProcessIntervalSeconds, 5, "This is the interval in which player events are processed in world or qs")
RULE_INT(Logging, BatchPlayerEventProcessChunkSize, 10000, "This is the cap of events that can be inserted into the queue before a force flush. This is to keep from hitting MySQL max_allowed_packet and killing the connection")
RULE_CATEGORY_END()
@@ -1094,6 +1095,7 @@ RULE_CATEGORY(Instances)
RULE_INT(Instances, ReservedInstances, 100, "Number of instance IDs which are reserved for globals. This value should not be changed while a server is running")
RULE_BOOL(Instances, RecycleInstanceIds, true, "Setting whether free instance IDs should be recycled to prevent them from gradually running out at 32k")
RULE_INT(Instances, GuildHallExpirationDays, 90, "Amount of days before a Guild Hall instance expires")
RULE_INT(Instances, ExpireOffsetTimeSeconds, 3600, "Amount of seconds to beyond instance expiration time we wait to purge the entry from the database. (Default: 1 Hour)")
RULE_CATEGORY_END()
RULE_CATEGORY(Expedition)
+2
View File
@@ -22,6 +22,7 @@ namespace ServerReload {
LevelEXPMods,
Logs,
Loot,
Maps,
Merchants,
NPCEmotes,
NPCSpells,
@@ -61,6 +62,7 @@ namespace ServerReload {
"Level EXP Mods",
"Logs",
"Loot",
"Maps",
"Merchants",
"NPC Emotes",
"NPC Spells",
-12
View File
@@ -2808,18 +2808,6 @@ bool IsLichSpell(uint16 spell_id)
);
}
bool IsValidSpellAndLoS(uint32 spell_id, bool has_los) {
if (!IsValidSpell(spell_id)) {
return false;
}
if (!has_los && IsTargetRequiredForSpell(spell_id)) {
return false;
}
return true;
}
bool IsInstantHealSpell(uint32 spell_id) {
if (!IsValidSpell(spell_id)) {
return false;
+4 -3
View File
@@ -900,8 +900,8 @@ const uint32 SPELL_TYPES_BENEFICIAL = (SpellType_Heal | SpellType_Buff | SpellTy
const uint32 SPELL_TYPES_INNATE = (SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root);
// Bot related functions
bool IsBotSpellTypeDetrimental (uint16 spell_type);
bool IsBotSpellTypeBeneficial (uint16 spell_type);
bool IsBotSpellTypeDetrimental(uint16 spell_type);
bool IsBotSpellTypeBeneficial(uint16 spell_type);
bool BotSpellTypeUsesTargetSettings(uint16 spell_type);
bool IsBotSpellTypeInnate (uint16 spell_type);
bool IsAEBotSpellType(uint16 spell_type);
@@ -917,6 +917,8 @@ bool IsCommandedBotSpellType(uint16 spell_type);
bool IsPullingBotSpellType(uint16 spell_type);
uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id);
uint16 GetPetBotSpellType(uint16 spell_type);
bool IsBotBuffSpellType(uint16 spell_type);
bool BotRequiresLoSToCast(uint16 spell_type, uint16 spell_id);
// These should not be used to determine spell category..
// They are a graphical affects (effects?) index only
@@ -1814,7 +1816,6 @@ bool IsEffectInSpell(uint16 spell_id, int effect_id);
uint16 GetSpellTriggerSpellID(uint16 spell_id, int effect_id);
bool IsBlankSpellEffect(uint16 spell_id, int effect_index);
bool IsValidSpell(uint32 spell_id);
bool IsValidSpellAndLoS(uint32 spell_id, bool has_los = true);
bool IsSummonSpell(uint16 spell_id);
bool IsDamageSpell(uint16 spell_id);
bool IsAnyDamageSpell(uint16 spell_id);
+28
View File
@@ -468,3 +468,31 @@ uint16 GetPetBotSpellType(uint16 spell_type) {
return spell_type;
}
bool IsBotBuffSpellType(uint16 spell_type) {
switch (spell_type) {
case BotSpellTypes::Buff:
case BotSpellTypes::PetBuffs:
case BotSpellTypes::ResistBuffs:
case BotSpellTypes::PetResistBuffs:
case BotSpellTypes::DamageShields:
case BotSpellTypes::PetDamageShields:
return true;
default:
return false;
}
return false;
}
bool BotRequiresLoSToCast(uint16 spell_type, uint16 spell_id) {
if (!BotSpellTypeRequiresTarget(spell_type)) {
return false;
}
if (!IsTargetRequiredForSpell(spell_id)) {
return false;
}
return true;
}
+22
View File
@@ -196,3 +196,25 @@ const uint32 Timer::SetCurrentTime()
return current_time;
}
const uint32 Timer::RollForward(uint32 seconds)
{
struct timeval read_time{};
uint32 this_time;
gettimeofday(&read_time, nullptr);
this_time = read_time.tv_sec * 1000 + read_time.tv_usec / 1000;
if (last_time == 0) {
current_time = 0;
}
else {
current_time += this_time - last_time;
}
last_time = this_time;
// Roll forward the specified number of seconds (converted to milliseconds)
current_time += seconds * 1000;
return current_time;
}
+1
View File
@@ -51,6 +51,7 @@ public:
inline uint32 GetDuration() { return(timer_time); }
static const uint32 SetCurrentTime();
static const uint32 RollForward(uint32 seconds);
static const uint32 GetCurrentTime();
static const uint32 GetTimeSeconds();
+3 -2
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "23.3.4-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "23.7.0-dev" // always append -dev to the current version for custom-builds
#define LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__
@@ -42,8 +42,9 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9314
#define CURRENT_BINARY_DATABASE_VERSION 9323
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
#define CUSTOM_BINARY_DATABASE_VERSION 0
#endif
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "23.3.4",
"version": "23.7.0",
"repository": {
"type": "git",
"url": "https://github.com/EQEmu/Server.git"
+1 -1
View File
@@ -177,7 +177,7 @@ int main()
}
if (player_event_process_timer.Check()) {
std::jthread player_event_thread(&PlayerEventLogs::Process, &player_event_logs);
player_event_logs.Process();
}
};
+2 -2
View File
@@ -177,7 +177,7 @@ void ChatChannelList::SendAllChannels(Client *c) {
std::string Message;
char CountString[10];
char CountString[13];
while(iterator.MoreElements()) {
@@ -408,7 +408,7 @@ void ChatChannel::SendChannelMembers(Client *c) {
if(!c) return;
char CountString[10];
char CountString[13];
sprintf(CountString, "(%i)", MemberCount(c->GetAccountStatus()));
+1
View File
@@ -57,6 +57,7 @@ echo "# Running NPC hand-in tests"
./bin/zone tests:npc-handins 2>&1 | tee test_output.log
./bin/zone tests:npc-handins-multiquest 2>&1 | tee -a test_output.log
./bin/zone tests:databuckets 2>&1 | tee -a test_output.log
./bin/zone tests:zone-state 2>&1 | tee -a test_output.log
if grep -E -q "QueryErr|Error|FAILED" test_output.log; then
echo "Error found in test output! Failing build."
+2 -2
View File
@@ -10,7 +10,7 @@ require (
require (
github.com/golang/protobuf v1.3.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.38.0 // 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=
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.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
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/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
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 --system-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_system.sql"
bash -c "${world_bin} database:dump --state-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_state.sql"
bash -c "${world_bin} database:dump --query-serv-tables --drop-table-syntax-only --dump-output-to-console > ${dump_path}drop_tables_queryserv.sql"
#############################################
# generate "create_" table files
@@ -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 --state-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_state.sql"
bash -c "${world_bin} database:dump --static-instance-data --dump-output-to-console >> ${dump_path}create_tables_state.sql"
bash -c "${world_bin} database:dump --query-serv-tables --table-structure-only --dump-output-to-console | sed 's/ AUTO_INCREMENT=[0-9]*\b//g' > ${dump_path}create_tables_queryserv.sql"
# with content
bash -c "${world_bin} database:dump --content-tables --dump-output-to-console > ${dump_path}create_tables_content.sql"
+3 -2
View File
@@ -12,8 +12,9 @@ void WorldserverCLI::DatabaseVersion(int argc, char **argv, argh::parser &cmd, s
Json::Value v;
v["database_version"] = CURRENT_BINARY_DATABASE_VERSION;
v["bots_database_version"] = RuleB(Bots, Enabled) ? CURRENT_BINARY_BOTS_DATABASE_VERSION : 0;
v["database_version"] = CURRENT_BINARY_DATABASE_VERSION;
v["bots_database_version"] = RuleB(Bots, Enabled) ? CURRENT_BINARY_BOTS_DATABASE_VERSION : 0;
v["custom_database_version"] = CUSTOM_BINARY_DATABASE_VERSION;
std::stringstream payload;
payload << v;
+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 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["enabled"] = event_settings[i].event_enabled ? true : false;
player_events["retention"] = event_settings[i].retention_days;
+6 -5
View File
@@ -11,11 +11,12 @@ void WorldserverCLI::Version(int argc, char **argv, argh::parser &cmd, std::stri
Json::Value j;
j["bots_database_version"] = CURRENT_BINARY_BOTS_DATABASE_VERSION;
j["compile_date"] = COMPILE_DATE;
j["compile_time"] = COMPILE_TIME;
j["database_version"] = CURRENT_BINARY_DATABASE_VERSION;
j["server_version"] = CURRENT_VERSION;
j["bots_database_version"] = CURRENT_BINARY_BOTS_DATABASE_VERSION;
j["compile_date"] = COMPILE_DATE;
j["compile_time"] = COMPILE_TIME;
j["custom_database_version"] = CUSTOM_BINARY_DATABASE_VERSION;
j["database_version"] = CURRENT_BINARY_DATABASE_VERSION;
j["server_version"] = CURRENT_VERSION;
std::stringstream payload;
payload << j;
+163 -51
View File
@@ -42,11 +42,16 @@ extern ZSList zoneserver_list;
uint32 numplayers = 0; //this really wants to be a member variable of ClientList...
ClientList::ClientList()
: CLStale_timer(10000)
: CLStale_timer(10000),
m_poll_cache_timer(6000)
{
NextCLEID = 1;
m_tick = std::make_unique<EQ::Timer>(5000, true, std::bind(&ClientList::OnTick, this, std::placeholders::_1));
// pre-allocate / pin memory for the zone server caches
m_gm_zone_server_ids.reserve(512);
m_guild_zone_server_ids.reserve(1024);
}
ClientList::~ClientList() {
@@ -57,6 +62,10 @@ void ClientList::Process() {
if (CLStale_timer.Check())
CLCheckStale();
if (m_poll_cache_timer.Check()) {
RebuildZoneServerCaches();
}
LinkedListIterator<Client*> iterator(list);
iterator.Reset();
@@ -384,6 +393,7 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
}
else {
cle->Update(zoneserver, scl);
AddToZoneServerCaches(cle);
}
return;
}
@@ -458,6 +468,7 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s
);
clientlist.Insert(cle);
AddToZoneServerCaches(cle);
zoneserver->ChangeWID(scl->charid, cle->GetID());
}
@@ -1608,7 +1619,7 @@ void ClientList::OnTick(EQ::Timer *t)
/**
* @param response
*/
void ClientList::GetClientList(Json::Value &response)
void ClientList::GetClientList(Json::Value &response, bool full_list)
{
LinkedListIterator<ClientListEntry *> Iterator(clientlist);
@@ -1619,62 +1630,68 @@ void ClientList::GetClientList(Json::Value &response)
Json::Value row;
row["account_id"] = cle->AccountID();
row["account_name"] = cle->AccountName();
row["admin"] = cle->Admin();
row["id"] = cle->GetID();
row["ip"] = cle->GetIP();
row["loginserver_account_id"] = cle->LSAccountID();
row["loginserver_id"] = cle->LSID();
row["loginserver_name"] = cle->LSName();
row["online"] = cle->Online();
row["world_admin"] = cle->WorldAdmin();
row["id"] = cle->GetID();
row["name"] = cle->name();
row["level"] = cle->level();
row["ip"] = cle->GetIP();
row["gm"] = cle->GetGM();
row["race"] = cle->race();
row["class"] = cle->class_();
row["client_version"] = cle->GetClientVersion();
row["admin"] = cle->Admin();
row["account_id"] = cle->AccountID();
row["account_name"] = cle->AccountName();
row["character_id"] = cle->CharID();
row["anon"] = cle->Anon();
row["guild_id"] = cle->GuildID();
if (full_list) {
row["loginserver_account_id"] = cle->LSAccountID();
row["loginserver_id"] = cle->LSID();
row["loginserver_name"] = cle->LSName();
row["online"] = cle->Online();
row["world_admin"] = cle->WorldAdmin();
row["guild_rank"] = cle->GuildRank();
row["guild_tribute_opt_in"] = cle->GuildTributeOptIn();
row["instance"] = cle->instance();
row["is_local_client"] = cle->IsLocalClient();
row["lfg"] = cle->LFG();
row["lfg_comments"] = cle->GetLFGComments();
row["lfg_from_level"] = cle->GetLFGFromLevel();
row["lfg_match_filter"] = cle->GetLFGMatchFilter();
row["lfg_to_level"] = cle->GetLFGToLevel();
row["tells_off"] = cle->TellsOff();
row["zone"] = cle->zone();
}
auto server = cle->Server();
if (server) {
row["server"]["client_address"] = server->GetCAddress();
row["server"]["client_local_address"] = server->GetCLocalAddress();
row["server"]["client_port"] = server->GetCPort();
row["server"]["compile_time"] = server->GetCompileTime();
row["server"]["id"] = server->GetID();
row["server"]["instance_id"] = server->GetInstanceID();
row["server"]["ip"] = server->GetIP();
row["server"]["is_booting"] = server->IsBootingUp();
row["server"]["launch_name"] = server->GetLaunchName();
row["server"]["launched_name"] = server->GetLaunchedName();
row["server"]["number_players"] = server->NumPlayers();
row["server"]["port"] = server->GetPort();
row["server"]["previous_zone_id"] = server->GetPrevZoneID();
row["server"]["static_zone"] = server->IsStaticZone();
row["server"]["uui"] = server->GetUUID();
row["server"]["zone_id"] = server->GetZoneID();
row["server"]["zone_long_name"] = server->GetZoneLongName();
row["server"]["zone_name"] = server->GetZoneName();
row["server"]["zone_os_pid"] = server->GetZoneOSProcessID();
row["server"]["zone_id"] = server->GetZoneID();
row["server"]["zone_long_name"] = server->GetZoneLongName();
row["server"]["zone_name"] = server->GetZoneName();
row["server"]["zone_os_pid"] = server->GetZoneOSProcessID();
row["server"]["id"] = server->GetID();
if (full_list) {
row["server"]["client_address"] = server->GetCAddress();
row["server"]["client_local_address"] = server->GetCLocalAddress();
row["server"]["client_port"] = server->GetCPort();
row["server"]["compile_time"] = server->GetCompileTime();
row["server"]["instance_id"] = server->GetInstanceID();
row["server"]["ip"] = server->GetIP();
row["server"]["is_booting"] = server->IsBootingUp();
row["server"]["launch_name"] = server->GetLaunchName();
row["server"]["launched_name"] = server->GetLaunchedName();
row["server"]["number_players"] = server->NumPlayers();
row["server"]["port"] = server->GetPort();
row["server"]["previous_zone_id"] = server->GetPrevZoneID();
row["server"]["static_zone"] = server->IsStaticZone();
row["server"]["uui"] = server->GetUUID();
}
}
else {
row["server"] = Json::Value();
}
row["anon"] = cle->Anon();
row["character_id"] = cle->CharID();
row["class"] = cle->class_();
row["client_version"] = cle->GetClientVersion();
row["gm"] = cle->GetGM();
row["guild_id"] = cle->GuildID();
row["guild_rank"] = cle->GuildRank();
row["guild_tribute_opt_in"] = cle->GuildTributeOptIn();
row["instance"] = cle->instance();
row["is_local_client"] = cle->IsLocalClient();
row["level"] = cle->level();
row["lfg"] = cle->LFG();
row["lfg_comments"] = cle->GetLFGComments();
row["lfg_from_level"] = cle->GetLFGFromLevel();
row["lfg_match_filter"] = cle->GetLFGMatchFilter();
row["lfg_to_level"] = cle->GetLFGToLevel();
row["name"] = cle->name();
row["race"] = cle->race();
row["tells_off"] = cle->TellsOff();
row["zone"] = cle->zone();
response.append(row);
@@ -1850,3 +1867,98 @@ std::map<uint32, ClientListEntry *> ClientList::GetGuildClientsWithTributeOptIn(
}
return guild_members;
}
void ClientList::RebuildZoneServerCaches()
{
// Clear without freeing memory (buckets stay allocated)
m_gm_zone_server_ids.clear();
m_guild_zone_server_ids.clear();
LinkedListIterator<ClientListEntry*> iterator(clientlist);
iterator.Reset();
while (iterator.MoreElements()) {
ClientListEntry* cle = iterator.GetData();
if (cle->Online() != CLE_Status::InZone || !cle->Server()) {
iterator.Advance();
continue;
}
uint32_t server_id = cle->Server()->GetID();
// Track GM zone server
if (cle->GetGM()) {
m_gm_zone_server_ids.insert(server_id);
}
// Track guild zone servers
if (cle->GuildID() > 0) {
auto& guild_set = m_guild_zone_server_ids[cle->GuildID()];
guild_set.insert(server_id);
}
iterator.Advance();
}
}
std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
{
if (RuleB(World, RealTimeCalculateGuilds)) {
std::vector<uint32_t> zone_server_ids;
std::unordered_set<uint32_t> seen_ids;
LinkedListIterator<ClientListEntry *> iterator(clientlist);
iterator.Reset();
while (iterator.MoreElements()) {
ClientListEntry *cle = iterator.GetData();
if (cle->Online() != CLE_Status::InZone) {
iterator.Advance();
continue;
}
if (!cle->Server()) {
iterator.Advance();
continue;
}
if (cle->GuildID() == guild_id) {
uint32_t id = cle->Server()->GetID();
if (seen_ids.insert(id).second) {
zone_server_ids.emplace_back(id);
}
}
iterator.Advance();
}
return zone_server_ids;
}
auto it = m_guild_zone_server_ids.find(guild_id);
if (it == m_guild_zone_server_ids.end()) {
return {};
}
return {it->second.begin(), it->second.end()};
}
void ClientList::AddToZoneServerCaches(ClientListEntry* cle)
{
if (!cle || cle->Online() != CLE_Status::InZone || !cle->Server()) {
return;
}
uint32_t server_id = cle->Server()->GetID();
// Add GM zone server if applicable
if (cle->GetGM()) {
m_gm_zone_server_ids.insert(server_id);
}
// Add guild zone server if applicable
if (cle->GuildID() > 0) {
m_guild_zone_server_ids[cle->GuildID()].insert(server_id);
}
}
+15 -1
View File
@@ -66,7 +66,7 @@ public:
int GetClientCount();
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 SendCharacterMessage(uint32_t character_id, int chat_type, const std::string& message);
@@ -76,6 +76,15 @@ public:
void SendCharacterMessageID(const std::string& character_name, int chat_type, int eqstr_id, std::initializer_list<std::string> args = {});
void SendCharacterMessageID(ClientListEntry* character, int chat_type, int eqstr_id, std::initializer_list<std::string> args = {});
void AddToZoneServerCaches(ClientListEntry* cle);
void RebuildZoneServerCaches();
std::vector<uint32_t> GetGuildZoneServers(uint32 guild_id);
inline std::vector<uint32_t> GetZoneServersWithGMs()
{
return {m_gm_zone_server_ids.begin(), m_gm_zone_server_ids.end()};
}
private:
void OnTick(EQ::Timer *t);
inline uint32 GetNextCLEID() { return NextCLEID++; }
@@ -90,6 +99,11 @@ private:
std::unique_ptr<EQ::Timer> m_tick;
// Zone server routing caches
Timer m_poll_cache_timer;
std::unordered_set<uint32_t> m_gm_zone_server_ids;
std::unordered_map<uint32_t, std::unordered_set<uint32_t>> m_guild_zone_server_ids;
};
#endif /*CLIENTLIST_H_*/
+5 -2
View File
@@ -137,8 +137,11 @@ void DynamicZone::SetSecondsRemaining(uint32_t seconds_remaining)
m_expire_time = now + new_remaining;
m_duration = std::chrono::duration_cast<std::chrono::seconds>(m_expire_time - m_start_time);
InstanceListRepository::UpdateDuration(database,
GetInstanceID(), static_cast<uint32_t>(m_duration.count()));
InstanceListRepository::UpdateDuration(
database,
GetInstanceID(),
static_cast<uint32_t>(m_duration.count())
);
SendZonesDurationUpdate(); // update zone caches and actual instance's timer
}
+24 -5
View File
@@ -32,6 +32,8 @@ void callGetZoneList(Json::Value &response)
row["client_address"] = zone->GetCAddress();
row["client_local_address"] = zone->GetCLocalAddress();
row["client_port"] = zone->GetCPort();
row["compile_version"] = zone->GetCurrentVersion();
row["compile_date"] = zone->GetCompileDate();
row["compile_time"] = zone->GetCompileTime();
row["id"] = zone->GetID();
row["instance_id"] = zone->GetInstanceID();
@@ -109,9 +111,17 @@ void callGetDatabaseSchema(Json::Value &response)
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)
@@ -125,6 +135,12 @@ void getReloadTypes(Json::Value &response)
}
}
void getServerCounts(Json::Value &response, const std::vector<std::string> &args)
{
response["zone_count"] = zoneserver_list.GetServerListCount();
response["client_count"] = client_list.GetClientCount();
}
void EQEmuApiWorldDataService::reload(Json::Value &r, const std::vector<std::string> &args)
{
std::vector<std::string> commands{};
@@ -146,7 +162,8 @@ void EQEmuApiWorldDataService::reload(Json::Value &r, const std::vector<std::str
for (auto &t: ServerReload::GetTypes()) {
if (std::to_string(t) == command || Strings::ToLower(ServerReload::GetName(t)) == command) {
message(r, fmt::format("Reloading [{}] globally", ServerReload::GetName(t)));
zoneserver_list.SendServerReload(t, nullptr);
LogInfo("Queueing reload of type [{}] to zones", ServerReload::GetName(t));
zoneserver_list.QueueServerReload(t);
}
found_command = true;
}
@@ -172,7 +189,7 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
callGetDatabaseSchema(r);
}
if (m == "get_client_list") {
callGetClientList(r);
callGetClientList(r, args);
}
if (m == "get_reload_types") {
getReloadTypes(r);
@@ -183,6 +200,9 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
if (m == "get_guild_details") {
callGetGuildDetails(r, args);
}
if (m == "get_server_counts") {
getServerCounts(r, args);
}
if (m == "lock_status") {
r["locked"] = WorldConfig::get()->Locked;
}
@@ -190,7 +210,6 @@ void EQEmuApiWorldDataService::get(Json::Value &r, const std::vector<std::string
void EQEmuApiWorldDataService::callGetGuildDetails(Json::Value &response, const std::vector<std::string> &args)
{
std::string command = !args[1].empty() ? args[1] : "";
if (command.empty()) {
return;
+1 -1
View File
@@ -286,7 +286,7 @@ void LoginServer::ProcessLSFatalError(uint16_t opcode, EQ::Net::Packet &p)
if (error.find("Worldserver Account / Password INVALID") != std::string::npos) {
reason = "Usually this indicates you do not have a valid [account] and [password] (worldserver) account associated with your loginserver configuration. ";
if (fmt::format("{}", m_loginserver_address).find("login.eqemulator.net") != std::string::npos) {
reason += "For Legacy EQEmulator connections, you need to register your server @ http://www.eqemulator.org/account/?LS";
reason += "For Legacy EQEmulator connections, you need to register your server @ https://www.eqemulator.org/index.php?pageid=ws_mgmt";
}
}
+11 -5
View File
@@ -381,11 +381,19 @@ int main(int argc, char **argv)
}
);
Timer player_event_process_timer(1000);
if (player_event_logs.LoadDatabaseConnection()) {
player_event_logs.Init();
}
auto event_log_processor = std::jthread([](const std::stop_token& stoken) {
while (!stoken.stop_requested()) {
if (!RuleB(Logging, PlayerEventsQSProcess)) {
player_event_logs.Process();
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
});
auto loop_fn = [&](EQ::Timer* t) {
Timer::SetCurrentTime();
@@ -448,10 +456,6 @@ int main(int argc, char **argv)
}
}
if (player_event_process_timer.Check()) {
std::jthread event_thread(&PlayerEventLogs::Process, &player_event_logs);
}
if (PurgeInstanceTimer.Check()) {
database.PurgeExpiredInstances();
database.PurgeAllDeletedDataBuckets();
@@ -502,6 +506,8 @@ int main(int argc, char **argv)
EQ::EventLoop::Get().Run();
event_log_processor.request_stop();
LogInfo("World main loop completed");
LogInfo("Shutting down zone connections (if any)");
zoneserver_list.KillAll();
+22 -17
View File
@@ -50,7 +50,7 @@ void WorldGuildManager::SendGuildRefresh(uint32 guild_id, bool name, bool motd,
s->motd_change = motd;
s->rank_change = rank;
s->relation_change = relation;
zoneserver_list.SendPacket(pack);
zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
safe_delete(pack);
}
@@ -61,7 +61,7 @@ void WorldGuildManager::SendCharRefresh(uint32 old_guild_id, uint32 guild_id, ui
s->guild_id = guild_id;
s->old_guild_id = old_guild_id;
s->char_id = charid;
zoneserver_list.SendPacket(pack);
zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
safe_delete(pack);
}
@@ -70,7 +70,7 @@ void WorldGuildManager::SendGuildDelete(uint32 guild_id) {
auto pack = new ServerPacket(ServerOP_DeleteGuild, sizeof(ServerGuildID_Struct));
ServerGuildID_Struct *s = (ServerGuildID_Struct *) pack->pBuffer;
s->guild_id = guild_id;
zoneserver_list.SendPacket(pack);
zoneserver_list.SendPacketToZonesWithGuild(guild_id, pack);
safe_delete(pack);
}
@@ -85,15 +85,14 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
ServerGuildRefresh_Struct *s = (ServerGuildRefresh_Struct *) pack->pBuffer;
LogGuilds("Received and broadcasting guild refresh for [{}], changes: name=[{}], motd=[{}], rank=d, relation=[{}]", s->guild_id, s->name_change, s->motd_change, s->rank_change, s->relation_change);
//broadcast this packet to all zones.
zoneserver_list.SendPacket(pack);
//preform a local refresh.
if(!RefreshGuild(s->guild_id)) {
LogGuilds("Unable to preform local refresh on guild [{}]", s->guild_id);
//can we do anything?
BaseGuildManager::RefreshGuild(s->guild_id);
}
//broadcast this packet to all zones.
zoneserver_list.SendPacketToBootedZones(pack);
break;
}
@@ -108,7 +107,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
//broadcast this update to any zone with a member in this guild.
//because im sick of this not working, sending it to all zones, just spends a bit more bandwidth.
zoneserver_list.SendPacket(pack);
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
break;
}
@@ -147,7 +146,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
auto s = (ServerGuildID_Struct *)pack->pBuffer;
RefreshGuild(s->guild_id);
zoneserver_list.SendPacket(pack);
zoneserver_list.SendPacketToZonesWithGuild(s->guild_id, pack);
break;
}
case ServerOP_GuildPermissionUpdate:
@@ -179,7 +178,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
sg->function_value
);
zoneserver_list.SendPacketToBootedZones(pack);
zoneserver_list.SendPacketToZonesWithGuild(sg->guild_id, pack);
}
else {
LogError("World Received ServerOP_GuildPermissionUpdate for guild [{}] function id {} with value of {} but guild could not be found.",
@@ -213,7 +212,7 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
rnc->rank,
rnc->rank_name
);
zoneserver_list.SendPacketToBootedZones(pack);
zoneserver_list.SendPacketToZonesWithGuild(rnc->guild_id, pack);
}
else {
LogError("World Received ServerOP_GuildRankNameChange from zone for guild [{}] rank id {} with new name of {} but could not find guild.",
@@ -230,13 +229,13 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
case ServerOP_GuildChannel:
case ServerOP_GuildURL:
case ServerOP_GuildMemberRemove:
case ServerOP_GuildSendGuildList:
case ServerOP_GuildMembersList:
{
zoneserver_list.SendPacketToBootedZones(pack);
auto in = (ServerOP_GuildMessage_Struct *) pack->pBuffer;
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, pack);
break;
}
case ServerOP_GuildMemberAdd:
case ServerOP_GuildMemberAdd:
{
auto in = (ServerOP_GuildMessage_Struct *)pack->pBuffer;
auto guild = GetGuildByGuildID(in->guild_id);
@@ -244,9 +243,15 @@ void WorldGuildManager::ProcessZonePacket(ServerPacket *pack) {
BaseGuildManager::RefreshGuild(in->guild_id);
}
zoneserver_list.SendPacketToBootedZones(pack);
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, pack);
break;
}
case ServerOP_GuildSendGuildList: {
auto in = (ServerOP_GuildMessage_Struct *) pack->pBuffer;
zoneserver_list.SendPacketToBootedZones(pack);
break;
}
default:
LogGuilds("Unknown packet {:#04x} received from zone??", pack->opcode);
break;
@@ -451,6 +456,6 @@ void WorldGuildManager::SendGuildTributeFavorAndTimer(uint32 guild_id, uint32 fa
data->tribute_timer = time;
data->trophy_timer = 0;
zoneserver_list.SendPacketToBootedZones(sp);
zoneserver_list.SendPacketToZonesWithGuild(guild_id, sp);
safe_delete(sp)
}
+246 -282
View File
@@ -29,6 +29,10 @@
#include "../common/repositories/inventory_repository.h"
#include "../common/repositories/criteria/content_filter_criteria.h"
#include "../common/zone_store.h"
#include "../common/repositories/character_data_repository.h"
#include "../common/repositories/character_bind_repository.h"
#include "../common/repositories/character_material_repository.h"
#include "../common/repositories/start_zones_repository.h"
WorldDatabase database;
WorldDatabase content_db;
@@ -50,187 +54,177 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o
character_limit = 8;
}
std::string character_list_query = fmt::format(
SQL(
SELECT
`id`,
`name`,
`gender`,
`race`,
`class`,
`level`,
`deity`,
`last_login`,
`time_played`,
`hair_color`,
`beard_color`,
`eye_color_1`,
`eye_color_2`,
`hair_style`,
`beard`,
`face`,
`drakkin_heritage`,
`drakkin_tattoo`,
`drakkin_details`,
`zone_id`
FROM
`character_data`
WHERE
`account_id` = {}
AND
`deleted_at` IS NULL
ORDER BY `name`
LIMIT {}
),
account_id,
character_limit
auto characters = CharacterDataRepository::GetWhere(
database,
fmt::format(
"`account_id` = {} AND `deleted_at` IS NULL ORDER BY `name` LIMIT {}",
account_id,
character_limit
)
);
auto results = database.QueryDatabase(character_list_query);
size_t character_count = results.RowCount();
if (character_count == 0) {
size_t character_count = characters.size();
if (characters.empty()) {
*out_app = new EQApplicationPacket(OP_SendCharInfo, sizeof(CharacterSelect_Struct));
CharacterSelect_Struct *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer;
cs->CharCount = 0;
auto *cs = (CharacterSelect_Struct *) (*out_app)->pBuffer;
cs->CharCount = 0;
cs->TotalChars = character_limit;
return;
}
std::vector<uint32_t> character_ids;
for (auto &e: characters) {
character_ids.push_back(e.id);
}
const auto& inventories = InventoryRepository::GetWhere(
*this,
fmt::format(
"`character_id` IN ({}) AND `slot_id` BETWEEN {} AND {}",
Strings::Join(character_ids, ","),
EQ::invslot::slotHead,
EQ::invslot::slotFeet
)
);
const auto& character_binds = CharacterBindRepository::GetWhere(
*this,
fmt::format(
"`id` IN ({}) ORDER BY id, slot",
Strings::Join(character_ids, ",")
)
);
const auto& character_materials = CharacterMaterialRepository::GetWhere(
*this,
fmt::format(
"`id` IN ({}) ORDER BY id, slot",
Strings::Join(character_ids, ",")
)
);
size_t packet_size = sizeof(CharacterSelect_Struct) + (sizeof(CharacterSelectEntry_Struct) * character_count);
*out_app = new EQApplicationPacket(OP_SendCharInfo, packet_size);
unsigned char *buff_ptr = (*out_app)->pBuffer;
CharacterSelect_Struct *cs = (CharacterSelect_Struct *) buff_ptr;
auto *cs = (CharacterSelect_Struct *) buff_ptr;
cs->CharCount = character_count;
cs->CharCount = character_count;
cs->TotalChars = character_limit;
buff_ptr += sizeof(CharacterSelect_Struct);
for (auto row = results.begin(); row != results.end(); ++row) {
CharacterSelectEntry_Struct *p_character_select_entry_struct = (CharacterSelectEntry_Struct *) buff_ptr;
PlayerProfile_Struct pp;
EQ::InventoryProfile inventory_profile;
for (auto &e: characters) {
auto *cse = (CharacterSelectEntry_Struct *) buff_ptr;
PlayerProfile_Struct pp;
EQ::InventoryProfile inv;
pp.SetPlayerProfileVersion(EQ::versions::ConvertClientVersionToMobVersion(client_version));
inventory_profile.SetInventoryVersion(client_version);
inventory_profile.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support
inv.SetInventoryVersion(client_version);
inv.SetGMInventory(true); // charsel can not interact with items..but, no harm in setting to full expansion support
uint32 character_id = Strings::ToUnsignedInt(row[0]);
uint8 has_home = 0;
uint8 has_bind = 0;
uint32 character_id = e.id;
uint8 has_home = 0;
uint8 has_bind = 0;
memset(&pp, 0, sizeof(PlayerProfile_Struct));
memset(p_character_select_entry_struct->Name, 0, sizeof(p_character_select_entry_struct->Name));
strcpy(p_character_select_entry_struct->Name, row[1]);
p_character_select_entry_struct->Class = (uint8) Strings::ToUnsignedInt(row[4]);
p_character_select_entry_struct->Race = (uint32) Strings::ToUnsignedInt(row[3]);
p_character_select_entry_struct->Level = (uint8) Strings::ToUnsignedInt(row[5]);
p_character_select_entry_struct->ShroudClass = p_character_select_entry_struct->Class;
p_character_select_entry_struct->ShroudRace = p_character_select_entry_struct->Race;
p_character_select_entry_struct->Zone = (uint16) Strings::ToUnsignedInt(row[19]);
p_character_select_entry_struct->Instance = 0;
p_character_select_entry_struct->Gender = (uint8) Strings::ToUnsignedInt(row[2]);
p_character_select_entry_struct->Face = (uint8) Strings::ToUnsignedInt(row[15]);
memset(cse->Name, 0, sizeof(cse->Name));
strcpy(cse->Name, e.name.c_str());
cse->Class = e.class_;
cse->Race = e.race;
cse->Level = e.level;
cse->ShroudClass = cse->Class;
cse->ShroudRace = cse->Race;
cse->Zone = e.zone_id;
cse->Instance = 0;
cse->Gender = e.gender;
cse->Face = e.face;
for (uint32 material_slot = 0; material_slot < EQ::textures::materialCount; material_slot++) {
p_character_select_entry_struct->Equip[material_slot].Material = 0;
p_character_select_entry_struct->Equip[material_slot].Unknown1 = 0;
p_character_select_entry_struct->Equip[material_slot].EliteModel = 0;
p_character_select_entry_struct->Equip[material_slot].HerosForgeModel = 0;
p_character_select_entry_struct->Equip[material_slot].Unknown2 = 0;
p_character_select_entry_struct->Equip[material_slot].Color = 0;
for (auto &s: cse->Equip) {
s.Material = 0;
s.Unknown1 = 0;
s.EliteModel = 0;
s.HerosForgeModel = 0;
s.Unknown2 = 0;
s.Color = 0;
}
p_character_select_entry_struct->Unknown15 = 0xFF;
p_character_select_entry_struct->Unknown19 = 0xFF;
p_character_select_entry_struct->DrakkinTattoo = (uint32) Strings::ToInt(row[17]);
p_character_select_entry_struct->DrakkinDetails = (uint32) Strings::ToInt(row[18]);
p_character_select_entry_struct->Deity = (uint32) Strings::ToInt(row[6]);
p_character_select_entry_struct->PrimaryIDFile = 0; // Processed Below
p_character_select_entry_struct->SecondaryIDFile = 0; // Processed Below
p_character_select_entry_struct->HairColor = (uint8) Strings::ToInt(row[9]);
p_character_select_entry_struct->BeardColor = (uint8) Strings::ToInt(row[10]);
p_character_select_entry_struct->EyeColor1 = (uint8) Strings::ToInt(row[11]);
p_character_select_entry_struct->EyeColor2 = (uint8) Strings::ToInt(row[12]);
p_character_select_entry_struct->HairStyle = (uint8) Strings::ToInt(row[13]);
p_character_select_entry_struct->Beard = (uint8) Strings::ToInt(row[14]);
p_character_select_entry_struct->GoHome = 0; // Processed Below
p_character_select_entry_struct->Tutorial = 0; // Processed Below
p_character_select_entry_struct->DrakkinHeritage = (uint32) Strings::ToInt(row[16]);
p_character_select_entry_struct->Unknown1 = 0;
p_character_select_entry_struct->Enabled = 1;
p_character_select_entry_struct->LastLogin = (uint32) Strings::ToInt(row[7]); // RoF2 value: 1212696584
p_character_select_entry_struct->Unknown2 = 0;
cse->Unknown15 = 0xFF;
cse->Unknown19 = 0xFF;
cse->DrakkinTattoo = e.drakkin_tattoo;
cse->DrakkinDetails = e.drakkin_details;
cse->Deity = e.deity;
cse->PrimaryIDFile = 0; // Processed Below
cse->SecondaryIDFile = 0; // Processed Below
cse->HairColor = e.hair_color;
cse->BeardColor = e.beard_color;
cse->EyeColor1 = e.eye_color_1;
cse->EyeColor2 = e.eye_color_2;
cse->HairStyle = e.hair_style;
cse->Beard = e.beard;
cse->GoHome = 0; // Processed Below
cse->Tutorial = 0; // Processed Below
cse->DrakkinHeritage = e.drakkin_heritage;
cse->Unknown1 = 0;
cse->Enabled = 1;
cse->LastLogin = e.last_login; // RoF2 value: 1212696584
cse->Unknown2 = 0;
if (RuleB(World, EnableReturnHomeButton)) {
int now = time(nullptr);
if ((now - Strings::ToInt(row[7])) >= RuleI(World, MinOfflineTimeToReturnHome))
p_character_select_entry_struct->GoHome = 1;
if (now - e.last_login >= RuleI(World, MinOfflineTimeToReturnHome)) {
cse->GoHome = 1;
}
}
if (RuleB(World, EnableTutorialButton) && (p_character_select_entry_struct->Level <= RuleI(World, MaxLevelForTutorial)))
p_character_select_entry_struct->Tutorial = 1;
if (RuleB(World, EnableTutorialButton) && (cse->Level <= RuleI(World, MaxLevelForTutorial))) {
cse->Tutorial = 1;
}
/**
* Bind
*/
character_list_query = fmt::format(
SQL(
SELECT
`zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`
FROM
`character_bind`
WHERE
`id` = {}
LIMIT 5
),
character_id
);
auto results_bind = database.QueryDatabase(character_list_query);
auto bind_count = results_bind.RowCount();
for (auto row_b = results_bind.begin(); row_b != results_bind.end(); ++row_b) {
if (row_b[6] && Strings::ToInt(row_b[6]) == 4) {
has_home = 1;
// If our bind count is less than 5, we need to actually make use of this data so lets parse it
if (bind_count < 5) {
pp.binds[4].zone_id = Strings::ToInt(row_b[0]);
pp.binds[4].instance_id = Strings::ToInt(row_b[1]);
pp.binds[4].x = Strings::ToFloat(row_b[2]);
pp.binds[4].y = Strings::ToFloat(row_b[3]);
pp.binds[4].z = Strings::ToFloat(row_b[4]);
pp.binds[4].heading = Strings::ToFloat(row_b[5]);
}
// binds
int bind_count = 0;
for (auto &bind : character_binds) {
if (bind.id != e.id) {
continue;
}
if (row_b[6] && Strings::ToInt(row_b[6]) == 0)
if (bind.slot == 4) {
has_home = 1;
pp.binds[4].zone_id = bind.zone_id;
pp.binds[4].instance_id = bind.instance_id;
pp.binds[4].x = bind.x;
pp.binds[4].y = bind.y;
pp.binds[4].z = bind.z;
pp.binds[4].heading = bind.heading;
}
if (bind.slot == 0) {
has_bind = 1;
}
bind_count++;
}
if (has_home == 0 || has_bind == 0) {
std::string character_list_query = fmt::format(
SQL(
SELECT
`zone_id`, `bind_id`, `x`, `y`, `z`, `heading`
FROM
`start_zones`
WHERE
`player_class` = {}
AND
`player_deity` = {}
AND
`player_race` = {} {}
),
p_character_select_entry_struct->Class,
p_character_select_entry_struct->Deity,
p_character_select_entry_struct->Race,
ContentFilterCriteria::apply().c_str()
const auto &start_zones = StartZonesRepository::GetWhere(
content_db,
fmt::format(
"`player_class` = {} AND `player_deity` = {} AND `player_race` = {} {}",
cse->Class,
cse->Deity,
cse->Race,
ContentFilterCriteria::apply().c_str()
)
);
auto results_bind = content_db.QueryDatabase(character_list_query);
for (auto row_d = results_bind.begin(); row_d != results_bind.end(); ++row_d) {
/* If a bind_id is specified, make them start there */
if (Strings::ToInt(row_d[1]) != 0) {
pp.binds[4].zone_id = (uint32) Strings::ToInt(row_d[1]);
if (!start_zones.empty()) {
pp.binds[4].zone_id = start_zones[0].zone_id;
pp.binds[4].x = start_zones[0].x;
pp.binds[4].y = start_zones[0].y;
pp.binds[4].z = start_zones[0].z;
pp.binds[4].heading = start_zones[0].heading;
if (start_zones[0].bind_id != 0) {
pp.binds[4].zone_id = start_zones[0].bind_id;
auto z = GetZone(pp.binds[4].zone_id);
if (z) {
pp.binds[4].x = z->safe_x;
@@ -238,168 +232,151 @@ void WorldDatabase::GetCharSelectInfo(uint32 account_id, EQApplicationPacket **o
pp.binds[4].z = z->safe_z;
pp.binds[4].heading = z->safe_heading;
}
}
/* Otherwise, use the zone and coordinates given */
else {
pp.binds[4].zone_id = (uint32) Strings::ToInt(row_d[0]);
float x = Strings::ToFloat(row_d[2]);
float y = Strings::ToFloat(row_d[3]);
float z = Strings::ToFloat(row_d[4]);
float heading = Strings::ToFloat(row_d[5]);
if (x == 0 && y == 0 && z == 0 && heading == 0) {
} else {
pp.binds[4].zone_id = start_zones[0].zone_id;
pp.binds[4].x = start_zones[0].x;
pp.binds[4].y = start_zones[0].y;
pp.binds[4].z = start_zones[0].z;
pp.binds[4].heading = start_zones[0].heading;
if (pp.binds[4].x == 0 && pp.binds[4].y == 0 && pp.binds[4].z == 0 && pp.binds[4].heading == 0) {
auto zone = GetZone(pp.binds[4].zone_id);
if (zone) {
x = zone->safe_x;
y = zone->safe_y;
z = zone->safe_z;
heading = zone->safe_heading;
pp.binds[4].x = zone->safe_x;
pp.binds[4].y = zone->safe_y;
pp.binds[4].z = zone->safe_z;
pp.binds[4].heading = zone->safe_heading;
}
}
pp.binds[4].x = x;
pp.binds[4].y = y;
pp.binds[4].z = z;
pp.binds[4].heading = heading;
}
}
pp.binds[0] = pp.binds[4];
/* If no home bind set, set it */
if (has_home == 0) {
std::string query = fmt::format(
SQL(
REPLACE INTO
`character_bind`
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`)
VALUES ({}, {}, {}, {}, {}, {}, {}, {})
),
character_id,
pp.binds[4].zone_id,
0,
pp.binds[4].x,
pp.binds[4].y,
pp.binds[4].z,
pp.binds[4].heading,
4
);
auto results_bset = QueryDatabase(query);
else {
LogError("No start zone found for class [{}] deity [{}] race [{}]", cse->Class, cse->Deity, cse->Race);
}
/* If no regular bind set, set it */
pp.binds[0] = pp.binds[4];
// If we don't have home set, set it
if (has_home == 0) {
auto bind = CharacterBindRepository::NewEntity();
bind.id = character_id;
bind.zone_id = pp.binds[4].zone_id;
bind.instance_id = 0;
bind.x = pp.binds[4].x;
bind.y = pp.binds[4].y;
bind.z = pp.binds[4].z;
bind.heading = pp.binds[4].heading;
bind.slot = 4;
CharacterBindRepository::ReplaceOne(*this, bind);
}
// If we don't have regular bind set, set it
if (has_bind == 0) {
std::string query = fmt::format(
SQL(
REPLACE INTO
`character_bind`
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`)
VALUES ({}, {}, {}, {}, {}, {}, {}, {})
),
character_id,
pp.binds[0].zone_id,
0,
pp.binds[0].x,
pp.binds[0].y,
pp.binds[0].z,
pp.binds[0].heading,
0
);
auto results_bset = QueryDatabase(query);
auto bind = CharacterBindRepository::NewEntity();
bind.id = character_id;
bind.zone_id = pp.binds[0].zone_id;
bind.instance_id = 0;
bind.x = pp.binds[0].x;
bind.y = pp.binds[0].y;
bind.z = pp.binds[0].z;
bind.heading = pp.binds[0].heading;
bind.slot = 0;
CharacterBindRepository::ReplaceOne(*this, bind);
}
}
/* If our bind count is less than 5, then we have null data that needs to be filled in. */
// If our bind count is less than 5, then we have null data that needs to be filled in
if (bind_count < 5) {
// we know that home and main bind must be valid here, so we don't check those
// we also use home to fill in the null data like live does.
std::vector<CharacterBindRepository::CharacterBind> binds;
for (int i = 1; i < 4; i++) {
if (pp.binds[i].zone_id != 0) // we assume 0 is the only invalid one ...
if (pp.binds[i].zone_id != 0) { // we assume 0 is the only invalid one ...
continue;
}
std::string query = fmt::format(
SQL(
REPLACE INTO
`character_bind`
(`id`, `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `slot`)
VALUES ({}, {}, {}, {}, {}, {}, {}, {})
),
character_id,
pp.binds[4].zone_id,
0,
pp.binds[4].x,
pp.binds[4].y,
pp.binds[4].z,
pp.binds[4].heading,
i
);
auto results_bset = QueryDatabase(query);
auto bind = CharacterBindRepository::NewEntity();
bind.slot = i;
bind.id = character_id;
bind.zone_id = pp.binds[4].zone_id;
bind.instance_id = 0;
bind.x = pp.binds[4].x;
bind.y = pp.binds[4].y;
bind.z = pp.binds[4].z;
bind.heading = pp.binds[4].heading;
binds.emplace_back(bind);
}
CharacterBindRepository::ReplaceMany(*this, binds);
}
character_list_query = fmt::format(
SQL(
SELECT
`slot`, `red`, `green`, `blue`, `use_tint`, `color`
FROM
`character_material`
WHERE
`id` = {}
),
character_id
);
auto results_b = database.QueryDatabase(character_list_query);
uint8 slot = 0;
for (auto row_b = results_b.begin(); row_b != results_b.end(); ++row_b) {
slot = Strings::ToInt(row_b[0]);
pp.item_tint.Slot[slot].Red = Strings::ToInt(row_b[1]);
pp.item_tint.Slot[slot].Green = Strings::ToInt(row_b[2]);
pp.item_tint.Slot[slot].Blue = Strings::ToInt(row_b[3]);
pp.item_tint.Slot[slot].UseTint = Strings::ToInt(row_b[4]);
for (auto &cm : character_materials) {
pp.item_tint.Slot[cm.slot].Red = cm.red;
pp.item_tint.Slot[cm.slot].Green = cm.green;
pp.item_tint.Slot[cm.slot].Blue = cm.blue;
pp.item_tint.Slot[cm.slot].UseTint = cm.use_tint;
pp.item_tint.Slot[cm.slot].Color = cm.color;
}
if (GetCharSelInventory(account_id, p_character_select_entry_struct->Name, &inventory_profile)) {
const EQ::ItemData *item = nullptr;
const EQ::ItemInstance *inst = nullptr;
int16 inventory_slot = 0;
if (GetCharSelInventory(inventories, e, &inv)) {
const EQ::ItemData *item = nullptr;
const EQ::ItemInstance *inst = nullptr;
int16 inventory_slot = 0;
for (uint32 matslot = EQ::textures::textureBegin; matslot < EQ::textures::materialCount; matslot++) {
inventory_slot = EQ::InventoryProfile::CalcSlotFromMaterial(matslot);
if (inventory_slot == INVALID_INDEX) { continue; }
inst = inventory_profile.GetItem(inventory_slot);
if (inst == nullptr)
inst = inv.GetItem(inventory_slot);
if (inst == nullptr) {
continue;
}
item = inst->GetItem();
if (item == nullptr)
if (item == nullptr) {
continue;
}
if (matslot > 6) {
uint32 item_id_file = 0;
// Weapon Models
if (inst->GetOrnamentationIDFile() != 0) {
item_id_file = inst->GetOrnamentationIDFile();
p_character_select_entry_struct->Equip[matslot].Material = item_id_file;
} else {
cse->Equip[matslot].Material = item_id_file;
}
else {
if (strlen(item->IDFile) > 2) {
item_id_file = Strings::ToInt(&item->IDFile[2]);
p_character_select_entry_struct->Equip[matslot].Material = item_id_file;
cse->Equip[matslot].Material = item_id_file;
}
}
if (matslot == EQ::textures::weaponPrimary) {
p_character_select_entry_struct->PrimaryIDFile = item_id_file;
} else {
p_character_select_entry_struct->SecondaryIDFile = item_id_file;
cse->PrimaryIDFile = item_id_file;
}
} else {
else {
cse->SecondaryIDFile = item_id_file;
}
}
else {
// Armor Materials/Models
uint32 color = (
pp.item_tint.Slot[matslot].UseTint ?
pp.item_tint.Slot[matslot].Color :
inst->GetColor()
pp.item_tint.Slot[matslot].Color :
inst->GetColor()
);
p_character_select_entry_struct->Equip[matslot].Material = item->Material;
p_character_select_entry_struct->Equip[matslot].EliteModel = item->EliteMaterial;
p_character_select_entry_struct->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot);
p_character_select_entry_struct->Equip[matslot].Color = color;
cse->Equip[matslot].Material = item->Material;
cse->Equip[matslot].EliteModel = item->EliteMaterial;
cse->Equip[matslot].HerosForgeModel = inst->GetOrnamentHeroModel(matslot);
cse->Equip[matslot].Color = color;
}
}
} else {
printf("Error loading inventory for %s\n", p_character_select_entry_struct->Name);
}
else {
printf("Error loading inventory for %s\n", cse->Name);
}
buff_ptr += sizeof(CharacterSelectEntry_Struct);
}
@@ -849,34 +826,21 @@ bool WorldDatabase::LoadCharacterCreateCombos()
return true;
}
// this is a slightly modified version of SharedDatabase::GetInventory(...) for character select use-only
bool WorldDatabase::GetCharSelInventory(uint32 account_id, char *name, EQ::InventoryProfile *inv)
bool WorldDatabase::GetCharSelInventory(
const std::vector<InventoryRepository::Inventory> &inventories,
const CharacterDataRepository::CharacterData &character,
EQ::InventoryProfile *inv
)
{
if (!account_id || !name || !inv) {
if (inventories.empty() || !character.id || !inv) {
return false;
}
const uint32 character_id = GetCharacterID(name);
for (const auto& e : inventories) {
if (e.character_id != character.id) {
continue;
}
if (!character_id) {
return false;
}
const auto& l = InventoryRepository::GetWhere(
*this,
fmt::format(
"`character_id` = {} AND `slot_id` BETWEEN {} AND {}",
character_id,
EQ::invslot::slotHead,
EQ::invslot::slotFeet
)
);
if (l.empty()) {
return true;
}
for (const auto& e : l) {
switch (e.slot_id) {
case EQ::invslot::slotFace:
case EQ::invslot::slotEar2:
+7 -1
View File
@@ -20,6 +20,8 @@
#include "../common/shareddb.h"
#include "../common/eq_packet.h"
#include "../common/repositories/inventory_repository.h"
#include "../common/repositories/character_data_repository.h"
struct PlayerProfile_Struct;
struct CharCreate_Struct;
@@ -43,7 +45,11 @@ private:
void SetTitaniumDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc);
void SetSoFDefaultStartZone(PlayerProfile_Struct* in_pp, CharCreate_Struct* in_cc);
bool GetCharSelInventory(uint32 account_id, char* name, EQ::InventoryProfile* inv);
bool GetCharSelInventory(
const std::vector<InventoryRepository::Inventory> &inventories,
const CharacterDataRepository::CharacterData &character,
EQ::InventoryProfile *inv
);
};
extern WorldDatabase database;
+90 -14
View File
@@ -36,11 +36,15 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "shared_task_manager.h"
#include "dynamic_zone_manager.h"
#include "ucs.h"
#include "clientlist.h"
#include "../common/repositories/trader_repository.h"
#include "../common/repositories/buyer_repository.h"
extern uint32 numzones;
extern EQ::Random emu_random;
extern WebInterfaceList web_interface;
extern SharedTaskManager shared_task_manager;
extern ClientList client_list;
volatile bool UCSServerAvailable_ = false;
void CatchSignal(int sig_num);
@@ -82,6 +86,8 @@ void ZSList::Remove(const std::string &uuid)
while (iter != zone_server_list.end()) {
if ((*iter)->GetUUID().compare(uuid) == 0) {
auto port = (*iter)->GetCPort();
(*iter)->CheckToClearTraderAndBuyerTables();
zone_server_list.erase(iter);
if (port != 0) {
@@ -126,6 +132,16 @@ void ZSList::Process() {
).c_str()
);
}
if (!m_queued_reloads.empty()) {
m_queued_reloads_mutex.lock();
for (auto &type : m_queued_reloads) {
LogInfo("Sending reload of type [{}] to zones", ServerReload::GetName(type));
SendServerReload(type, nullptr);
}
m_queued_reloads.clear();
m_queued_reloads_mutex.unlock();
}
}
bool ZSList::SendPacket(ServerPacket* pack) {
@@ -515,19 +531,27 @@ void ZSList::SendEmoteMessage(const char* to, uint32 to_guilddbid, int16 to_mins
SendEmoteMessageRaw(to, to_guilddbid, to_minstatus, type, buffer);
}
void ZSList::SendEmoteMessageRaw(const char* to, uint32 to_guilddbid, int16 to_minstatus, uint32 type, const char* message) {
if (!message)
void ZSList::SendEmoteMessageRaw(
const char *to,
uint32 to_guilddbid,
int16 to_minstatus,
uint32 type,
const char *message
)
{
if (!message) {
return;
}
auto pack = new ServerPacket;
pack->opcode = ServerOP_EmoteMessage;
pack->size = sizeof(ServerEmoteMessage_Struct) + strlen(message) + 1;
pack->opcode = ServerOP_EmoteMessage;
pack->size = sizeof(ServerEmoteMessage_Struct) + strlen(message) + 1;
pack->pBuffer = new uchar[pack->size];
memset(pack->pBuffer, 0, pack->size);
ServerEmoteMessage_Struct* sem = (ServerEmoteMessage_Struct*)pack->pBuffer;
ServerEmoteMessage_Struct *sem = (ServerEmoteMessage_Struct *) pack->pBuffer;
if (to) {
strcpy((char *)sem->to, to);
strcpy((char *) sem->to, to);
}
else {
sem->to[0] = 0;
@@ -535,22 +559,37 @@ void ZSList::SendEmoteMessageRaw(const char* to, uint32 to_guilddbid, int16 to_m
sem->guilddbid = to_guilddbid;
sem->minstatus = to_minstatus;
sem->type = type;
sem->type = type;
strcpy(&sem->message[0], message);
char tempto[64] = { 0 };
if (to)
char tempto[64] = {0};
if (to) {
strn0cpy(tempto, to, 64);
}
if (tempto[0] == 0) {
SendPacket(pack);
if (to_guilddbid > 0) {
SendPacketToZonesWithGuild(to_guilddbid, pack);
}
else if (to_minstatus > 0) {
SendPacketToZonesWithGMs(pack);
} else {
SendPacket(pack);
}
}
else {
ZoneServer* zs = FindByName(to);
if (zs != 0)
ZoneServer *zs = FindByName(to);
if (zs) {
zs->SendPacket(pack);
else
}
else if (to_guilddbid > 0) {
SendPacketToZonesWithGuild(to_guilddbid, pack);
}
else if (to_minstatus > 0) {
SendPacketToZonesWithGMs(pack);
}
else {
SendPacket(pack);
}
}
delete pack;
}
@@ -871,6 +910,34 @@ bool ZSList::SendPacketToBootedZones(ServerPacket* pack)
return true;
}
bool ZSList::SendPacketToZonesWithGuild(uint32 guild_id, ServerPacket* pack)
{
auto servers = client_list.GetGuildZoneServers(guild_id);
for (auto const& z : zone_server_list) {
for (auto const& server_id : servers) {
if (z->GetID() == server_id && z->GetZoneID() > 0) {
z->SendPacket(pack);
}
}
}
return true;
}
bool ZSList::SendPacketToZonesWithGMs(ServerPacket* pack)
{
auto servers = client_list.GetZoneServersWithGMs();
for (auto const &z: zone_server_list) {
for (auto const &server_id: servers) {
if (z->GetID() == server_id && z->GetZoneID() > 0) {
z->SendPacket(pack);
}
}
}
return true;
}
void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
{
static auto pack = ServerPacket(ServerOP_ServerReloadRequest, sizeof(ServerReload::Request));
@@ -894,6 +961,8 @@ void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
ServerReload::Type::Commands,
ServerReload::Type::PerlExportSettings,
ServerReload::Type::DataBucketsCache,
ServerReload::Type::Quests,
ServerReload::Type::QuestsTimerReset,
ServerReload::Type::WorldRepop,
ServerReload::Type::WorldWithRespawn
};
@@ -944,3 +1013,10 @@ void ZSList::SendServerReload(ServerReload::Type type, uchar *packet)
++counter;
}
}
void ZSList::QueueServerReload(ServerReload::Type &type)
{
m_queued_reloads_mutex.lock();
m_queued_reloads.emplace_back(type);
m_queued_reloads_mutex.unlock();
}
+7
View File
@@ -9,6 +9,7 @@
#include <vector>
#include <memory>
#include <deque>
#include <mutex>
class WorldTCPConnection;
class ServerPacket;
@@ -29,6 +30,8 @@ public:
bool SendPacket(ServerPacket *pack);
bool SendPacket(uint32 zoneid, ServerPacket *pack);
bool SendPacket(uint32 zoneid, uint16 instanceid, ServerPacket *pack);
bool SendPacketToZonesWithGuild(uint32 guild_id, ServerPacket *pack);
bool SendPacketToZonesWithGMs(ServerPacket *pack);
bool SendPacketToBootedZones(ServerPacket* pack);
bool SetLockedZone(uint16 iZoneID, bool iLock);
@@ -70,8 +73,12 @@ public:
ZoneServer* FindByZoneID(uint32 ZoneID);
const std::list<std::unique_ptr<ZoneServer>> &getZoneServerList() const;
inline uint32_t GetServerListCount() { return zone_server_list.size(); }
void SendServerReload(ServerReload::Type type, uchar *packet = nullptr);
std::mutex m_queued_reloads_mutex;
std::vector<ServerReload::Type> m_queued_reloads = {};
void QueueServerReload(ServerReload::Type &type);
private:
void OnTick(EQ::Timer *t);
uint32 NextID;
+34 -8
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/skill_caps.h"
#include "../common/server_reload_types.h"
#include "../common/repositories/trader_repository.h"
#include "../common/repositories/buyer_repository.h"
extern ClientList client_list;
extern GroupLFPList LFPGroupList;
@@ -571,7 +573,13 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
);
}
}
zoneserver_list.SendPacket(pack);
if (scm->guilddbid > 0) {
zoneserver_list.SendPacketToZonesWithGuild(scm->guilddbid, pack);
} else if (scm->chan_num == ChatChannel_GMSAY) {
zoneserver_list.SendPacketToZonesWithGMs(pack);
} else {
zoneserver_list.SendPacket(pack);
}
}
break;
@@ -729,13 +737,15 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
zs = zoneserver_list.FindByID(s->zone_server_id);
} else if (s->zone_id) {
zs = zoneserver_list.FindByName(ZoneName(s->zone_id));
} else if (s->instance_id) {
zs = zoneserver_list.FindByInstanceID(s->instance_id);
} else {
zoneserver_list.SendEmoteMessage(
s->admin_name,
0,
AccountStatus::Player,
Chat::White,
"Error: SOP_ZoneShutdown: neither ID nor name specified"
"Error: SOP_ZoneShutdown: Zone ID, Instance ID, nor Zone Short Name specified"
);
}
@@ -1510,7 +1520,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
guild->tribute.timer.Disable();
zoneserver_list.SendPacketToBootedZones(pack);
zoneserver_list.SendPacketToZonesWithGuild(data->guild_id, pack);
}
break;
}
@@ -1553,7 +1563,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
guild_mgr.UpdateDbGuildFavor(data->guild_id, data->favor);
guild_mgr.UpdateDbTributeTimeRemaining(data->guild_id, data->time_remaining);
zoneserver_list.SendPacketToBootedZones(pack);
zoneserver_list.SendPacketToZonesWithGuild(data->guild_id, pack);
}
break;
}
@@ -1587,7 +1597,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
data->time_remaining = in->time_remaining;
strn0cpy(data->player_name, in->player_name, sizeof(data->player_name));
zoneserver_list.SendPacketToBootedZones(out);
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, out);
safe_delete(out);
}
break;
@@ -1610,7 +1620,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
out->tribute_id_2_tier = guild->tribute.id_2_tier;
out->time_remaining = guild_mgr.GetGuildTributeTimeRemaining(in->guild_id);
zoneserver_list.SendPacketToBootedZones(sp);
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, sp);
safe_delete(sp);
}
@@ -1630,7 +1640,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
out->tribute_timer = guild_mgr.GetGuildTributeTimeRemaining(in->guild_id);
out->trophy_timer = 0;
zoneserver_list.SendPacketToBootedZones(sp);
zoneserver_list.SendPacketToZonesWithGuild(in->guild_id, sp);
safe_delete(sp);
}
@@ -1654,7 +1664,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
out->member_time = in->member_time;
strn0cpy(out->player_name, in->player_name, sizeof(out->player_name));
zoneserver_list.SendPacketToBootedZones(sp);
zoneserver_list.SendPacketToZonesWithGuild(out->guild_id, sp);
safe_delete(sp)
}
break;
@@ -1852,3 +1862,19 @@ void ZoneServer::IncomingClient(Client* client) {
SendPacket(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()
);
}
}
+3
View File
@@ -53,6 +53,9 @@ public:
inline const char* GetZoneName() const { return zone_name; }
inline const char* GetZoneLongName() const { return long_name; }
inline std::string GetCurrentVersion() const { return CURRENT_VERSION; }
void CheckToClearTraderAndBuyerTables();
inline std::string GetCompileDate() const { return COMPILE_DATE; }
const char* GetCompileTime() const{ return compiled; }
void SetCompile(char* in_compile){ strcpy(compiled,in_compile); }
inline uint32 GetZoneID() const { return zone_server_zone_id; }
+2
View File
@@ -173,6 +173,7 @@ SET(zone_sources
zone_event_scheduler.cpp
zone_npc_factions.cpp
zone_reload.cpp
zone_save_state.cpp
zoning.cpp
)
@@ -292,6 +293,7 @@ SET(zone_headers
zonedump.h
zone_cli.h
zone_reload.h
zone_save_state.h
zone_cli.cpp)
ADD_EXECUTABLE(zone ${zone_sources} ${zone_headers})
+48 -27
View File
@@ -2507,6 +2507,12 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
return false;
}
if (m_resumed_from_zone_suspend && !IsQueuedForCorpse()) {
LogInfo("NPC [{}] is resumed from zone suspend, cannot kill until zone resume is complete.", GetCleanName());
SetHP(0);
return false;
}
if (IsMultiQuestEnabled()) {
for (auto &i: m_hand_in.items) {
if (i.is_multiquest_item && i.item->GetItem()->NoDrop != 0) {
@@ -2627,7 +2633,7 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
bool pet_owner_is_client = give_exp->IsPet() && owner->IsClient();
bool pet_owner_is_bot = give_exp->IsPet() && owner->IsBot();
bool owner_is_client = owner->IsClient();
bool is_in_same_group_or_raid = (
pet_owner_is_client ||
(pet_owner_is_bot && owner->IsInGroupOrRaid(ulimate_owner)) ||
@@ -3062,11 +3068,11 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
else
SetAssistAggro(true);
bool wasengaged = IsEngaged();
bool was_engaged = IsEngaged();
Mob* owner = other->GetOwner();
Mob* mypet = GetPet();
Mob* myowner = GetOwner();
Mob* targetmob = GetTarget();
Mob* my_pet = GetPet();
Mob* my_owner = GetOwner();
Mob* target_mob = GetTarget();
bool on_hatelist = CheckAggro(other);
AddRampage(other);
@@ -3095,7 +3101,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
if (IsPet()) {
if ((IsGHeld() || (IsHeld() && IsFocused())) && !on_hatelist) // we want them to be able to climb the hate list
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;
}
}
@@ -3128,7 +3134,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
return;
}
if (other == myowner) {
if (other == my_owner) {
return;
}
@@ -3230,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 (
!mypet->IsFamiliar() &&
!mypet->GetSpecialAbility(SpecialAbility::AggroImmunity) &&
!(IsBot() && mypet->GetSpecialAbility(SpecialAbility::BotAggroImmunity)) &&
!(IsClient() && mypet->GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) &&
!(IsNPC() && mypet->GetSpecialAbility(SpecialAbility::NPCAggroImmunity))
) {
mypet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
if (my_pet) {
bool aggro_immunity = my_pet->GetSpecialAbility(SpecialAbility::AggroImmunity);
bool bot_aggro_immunity = IsBot() && my_pet->GetSpecialAbility(SpecialAbility::BotAggroImmunity);
bool client_aggro_immunity = IsClient() && my_pet->GetSpecialAbility(SpecialAbility::ClientAggroImmunity);
bool npc_aggro_immunity = IsNPC() && my_pet->GetSpecialAbility(SpecialAbility::NPCAggroImmunity);
bool can_add_to_hatelist = !my_pet->IsFamiliar() &&
!aggro_immunity &&
!bot_aggro_immunity &&
!client_aggro_immunity &&
!npc_aggro_immunity;
if (can_add_to_hatelist) {
bool bot_with_controllable_pet = IsBot() && CastToBot()->HasControllablePet(BotAnimEmpathy::Attack);
if (!IsBot() || bot_with_controllable_pet) {
my_pet->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
}
}
}
else if (myowner) { // I am a pet, add other to owner if it's NPC/LD
if (
myowner->IsAIControlled() &&
!myowner->GetSpecialAbility(SpecialAbility::AggroImmunity) &&
!(myowner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity)) &&
!(myowner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) &&
!(myowner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity))
) {
myowner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
else if (my_owner) { // I am a pet, add other to owner if it's NPC/LD
if (my_owner->IsAIControlled()) {
bool aggro_immunity = my_owner->GetSpecialAbility(SpecialAbility::AggroImmunity);
bool bot_aggro_immunity = my_owner->IsBot() && GetSpecialAbility(SpecialAbility::BotAggroImmunity);
bool client_aggro_immunity = my_owner->IsClient() && GetSpecialAbility(SpecialAbility::ClientAggroImmunity);
bool npc_aggro_immunity = my_owner->IsNPC() && GetSpecialAbility(SpecialAbility::NPCAggroImmunity);
bool can_add_to_hatelist = !aggro_immunity &&
!bot_aggro_immunity &&
!client_aggro_immunity &&
!npc_aggro_immunity;
if (can_add_to_hatelist) {
my_owner->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
}
}
}
@@ -3258,7 +3277,7 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
entity_list.AddTempPetsToHateList(this, other, bFrenzy);
}
if (!wasengaged) {
if (!was_engaged) {
if (IsNPC() && other->IsClient() && other->CastToClient()) {
if (parse->HasQuestSub(GetNPCTypeID(), EVENT_AGGRO)) {
parse->EventNPC(EVENT_AGGRO, CastToNPC(), other, "", 0);
@@ -6671,7 +6690,9 @@ void Client::SetAttackTimer()
else
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) {
primary_speed = speed;
+9 -12
View File
@@ -1165,10 +1165,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
}
case SE_ResistFearChance: {
if (base_value == 100) // If we reach 100% in a single spell/item then we should be immune to
// negative fear resist effects until our immunity is over
newbon->Fearless = true;
newbon->ResistFearChance += base_value; // these should stack
break;
}
@@ -2474,9 +2470,6 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
case SE_ResistFearChance:
{
if(effect_value == 100) // If we reach 100% in a single spell/item then we should be immune to negative fear resist effects until our immunity is over
new_bonus->Fearless = true;
new_bonus->ResistFearChance += effect_value; // these should stack
break;
}
@@ -4689,11 +4682,7 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
break;
case SE_ResistFearChance:
if (negate_spellbonus) {
spellbonuses.Fearless = false;
spellbonuses.ResistFearChance = effect_value;
}
if (negate_spellbonus) {spellbonuses.ResistFearChance = effect_value; }
if (negate_aabonus) { aabonuses.ResistFearChance = effect_value; }
if (negate_itembonus) { itembonuses.ResistFearChance = effect_value; }
break;
@@ -5331,6 +5320,14 @@ void Mob::NegateSpellEffectBonuses(uint16 spell_id)
spellbonuses.SEResist[e] = effect_value;
spellbonuses.SEResist[e + 1] = effect_value;
}
if (negate_itembonus) {
itembonuses.SEResist[e] = effect_value;
itembonuses.SEResist[e + 1] = effect_value;
}
if (negate_aabonus) {
aabonuses.SEResist[e] = effect_value;
aabonuses.SEResist[e + 1] = effect_value;
}
}
break;
}
+372 -326
View File
File diff suppressed because it is too large Load Diff
+14 -31
View File
@@ -39,9 +39,6 @@
constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds
constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MIN = 1500; // 1.5 seconds
constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MAX = 3000; // 3 seconds
constexpr uint32 MAG_EPIC_1_0 = 28034;
extern WorldServer worldserver;
@@ -232,19 +229,10 @@ static std::map<uint16, std::string> botSubType_names = {
{ CommandedSubTypes::Selo, "Selo" }
};
struct CombatRangeInput {
Mob* target;
float target_distance;
uint8 stop_melee_level;
const EQ::ItemInstance* p_item;
const EQ::ItemInstance* s_item;
};
struct CombatRangeOutput {
bool at_combat_range = false;
float melee_distance_min = 0.0f;
float melee_distance = 0.0f;
float melee_distance_max = 0.0f;
namespace BotAnimEmpathy {
constexpr uint8 Guard = 1;
constexpr uint8 Attack = 2;
constexpr uint8 BackOff = 3;
};
class Bot : public NPC {
@@ -577,7 +565,7 @@ public:
uint16 GetPetBotSpellType(uint16 spell_type);
// 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);
void SetSpellTargetList(std::vector<Mob*> spell_target_list) { _spell_target_list = 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 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 GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type = BotSpellTypes::Pet);
static std::string GetBotMagicianPetType(Bot* caster);
@@ -804,6 +792,7 @@ public:
EQ::ItemInstance* GetBotItem(uint16 slot_id);
bool GetSpawnStatus() { return _spawnStatus; }
uint8 GetPetChooserID() { return _petChooserID; }
bool HasControllablePet(uint8 ranks_required = 0);
bool IsBotRanged() { return _botRangedSetting; }
bool IsBotCharmer() { return _botCharmer; }
bool IsBot() const override { return true; }
@@ -1102,15 +1091,8 @@ public:
bool CheckIfCasting(float fm_distance);
void HealRotationChecks();
bool GetCombatJitterFlag() { return m_combat_jitter_flag; }
void SetCombatJitterFlag(bool flag = true) { m_combat_jitter_flag = flag; }
bool GetCombatOutOfRangeJitterFlag() { return m_combat_out_of_range_jitter_flag; }
void SetCombatOutOfRangeJitterFlag(bool flag = true) { m_combat_out_of_range_jitter_flag = flag; }
void SetCombatJitter();
void SetCombatOutOfRangeJitter();
void DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stop_melee_level, float tar_distance, float melee_distance_min, float melee_distance, float melee_distance_max, bool behind_mob, bool front_mob);
void DoFaceCheckWithJitter(Mob* tar);
void DoFaceCheckNoJitter(Mob* tar);
bool DoCombatPositioning(const CombatPositioningInput& input);
void RunToGoalWithJitter(glm::vec3 Goal);
bool RequiresLoSForPositioning();
bool HasRequiredLoSForPositioning(Mob* tar);
@@ -1118,18 +1100,21 @@ public:
// Try Combat Methods
bool TryEvade(Mob* tar);
bool TryFacingTarget(Mob* tar);
bool TryPursueTarget(float leash_distance, glm::vec3& Goal);
bool TryPursueTarget(float leash_distance);
bool TryMeditate();
bool TryAutoDefend(Client* bot_owner, float leash_distance);
bool TryIdleChecks(float fm_distance);
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal);
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance);
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob);
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance);
bool TryBardMovementCasts();
bool BotRangedAttack(Mob* other, bool can_double_attack = false);
bool CheckDoubleRangedAttack();
// Public "Refactor" Methods
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:
void BotMeditate(bool is_sitting);
@@ -1189,8 +1174,6 @@ private:
Timer m_auto_save_timer;
Timer m_combat_jitter_timer;
bool m_combat_jitter_flag;
bool m_combat_out_of_range_jitter_flag;
bool m_dirtyautohaters;
bool m_guard_flag;
+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;
!database.botdb.QueryNameAvailablity(bot_name, available_flag);
!database.botdb.QueryNameAvailability(bot_name, available_flag);
if (!available_flag) {
bot_owner->Message(
@@ -517,88 +517,31 @@ uint32 helper_bot_create(Client *bot_owner, std::string bot_name, uint8 bot_clas
return bot_id;
}
auto bot_creation_limit = bot_owner->GetBotCreationLimit();
auto bot_creation_limit_class = bot_owner->GetBotCreationLimit(bot_class);
if (!Bot::CheckHighEnoughLevelForBots(bot_owner)) {
return bot_id;
}
if (!Bot::CheckHighEnoughLevelForBots(bot_owner, bot_class)) {
return bot_id;
}
uint32 bot_count = 0;
uint32 bot_class_count = 0;
if (!database.botdb.QueryBotCount(bot_owner->CharacterID(), bot_class, bot_count, bot_class_count)) {
bot_owner->Message(Chat::Yellow, "Failed to query bot count.");
return bot_id;
}
if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) {
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());
if (!Bot::CheckCreateLimit(bot_owner, bot_count)) {
return bot_id;
}
if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_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());
if (!Bot::CheckCreateLimit(bot_owner, bot_class_count, bot_class)) {
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);
if (!my_bot->Save()) {
+1 -1
View File
@@ -22,7 +22,7 @@ void bot_command_attack(Client *c, const Seperator *sep)
return;
}
if (!c->DoLosChecks(target_mob)) {
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) {
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
return;
}
+29 -121
View File
@@ -130,7 +130,7 @@ void bot_command_clone(Client *c, const Seperator *sep)
bool available_flag = false;
!database.botdb.QueryNameAvailablity(bot_name, available_flag);
!database.botdb.QueryNameAvailability(bot_name, available_flag);
if (!available_flag) {
c->Message(
@@ -144,55 +144,25 @@ void bot_command_clone(Client *c, const Seperator *sep)
return;
}
auto bot_creation_limit = c->GetBotCreationLimit();
auto bot_creation_limit_class = c->GetBotCreationLimit(my_bot->GetClass());
uint32 bot_count = 0;
uint32 bot_class_count = 0;
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;
}
if (bot_creation_limit >= 0 && bot_count >= bot_creation_limit) {
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());
if (!Bot::CheckCreateLimit(c, bot_count)) {
return;
}
if (bot_creation_limit_class >= 0 && bot_class_count >= bot_creation_limit_class) {
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());
if (!Bot::CheckCreateLimit(c, bot_class_count, my_bot->GetClass())) {
return;
}
uint32 clone_id = 0;
if (!database.botdb.CreateCloneBot(my_bot->GetBotID(), bot_name, clone_id) || !clone_id) {
c->Message(
Chat::White,
@@ -205,6 +175,7 @@ void bot_command_clone(Client *c, const Seperator *sep)
}
int clone_stance = Stance::Passive;
if (!database.botdb.LoadStance(my_bot->GetBotID(), clone_stance)) {
c->Message(
Chat::White,
@@ -729,6 +700,7 @@ void bot_command_list_bots(Client *c, const Seperator *sep)
return;
}
int NO_BOT_LIMIT = -1;
bool Account = false;
int seps = 1;
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++) {
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(
Chat::White,
fmt::format(
@@ -938,20 +910,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
return;
}
auto bot_character_level = c->GetBotRequiredLevel();
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()
);
if (!Bot::CheckHighEnoughLevelForBots(c)) {
return;
}
@@ -959,27 +918,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
return;
}
auto bot_spawn_limit = c->GetBotSpawnLimit();
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());
if (!Bot::CheckSpawnLimit(c)) {
return;
}
@@ -1004,52 +943,6 @@ void bot_command_spawn(Client *c, const Seperator *sep)
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) {
c->Message(
Chat::White,
@@ -1061,6 +954,14 @@ void bot_command_spawn(Client *c, const Seperator *sep)
return;
}
if (!Bot::CheckHighEnoughLevelForBots(c, bot_class)) {
return;
}
if (!Bot::CheckSpawnLimit(c, bot_class)) {
return;
}
if (entity_list.GetMobByBotID(bot_id)) {
c->Message(
Chat::White,
@@ -1069,6 +970,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
bot_name
).c_str()
);
return;
}
@@ -1083,6 +985,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
bot_id
).c_str()
);
return;
}
@@ -1097,6 +1000,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
);
safe_delete(my_bot);
return;
}
@@ -1121,6 +1025,7 @@ void bot_command_spawn(Client *c, const Seperator *sep)
};
uint8 message_index = 0;
if (c->GetBotOption(Client::booSpawnMessageClassSpecific)) {
message_index = VALIDATECLASSID(my_bot->GetClass());
}
@@ -1560,8 +1465,11 @@ void bot_command_summon(Client *c, const Seperator *sep)
continue;
}
bot_iter->GetPet()->WipeHateList();
bot_iter->GetPet()->SetTarget(nullptr);
if (bot_iter->HasControllablePet(BotAnimEmpathy::BackOff)) {
bot_iter->GetPet()->WipeHateList();
bot_iter->GetPet()->SetTarget(nullptr);
}
bot_iter->GetPet()->Teleport(c->GetPosition());
}
+8 -1
View File
@@ -525,6 +525,9 @@ void bot_command_cast(Client* c, const Seperator* sep)
continue;
}
bool requires_los = !(IsAnyHealSpell(spell_id) && !IsPBAESpell(spell_id));
bot_iter->SetHasLoS(requires_los ? bot_iter->DoLosChecks(new_tar) : true);
if (!bot_iter->AttemptAACastSpell(tar, spell_id, rank)) {
continue;
}
@@ -543,6 +546,9 @@ void bot_command_cast(Client* c, const Seperator* sep)
tar = bot_iter;
}
bool los_required = bot_iter != tar && !IsAnyHealSpell(chosen_spell_id) && !IsPBAESpell(chosen_spell_id);
bot_iter->SetHasLoS(los_required ? bot_iter->DoLosChecks(new_tar) : true);
if (bot_iter->AttemptForcedCastSpell(tar, chosen_spell_id)) {
if (!first_found) {
first_found = bot_iter;
@@ -556,7 +562,8 @@ void bot_command_cast(Client* c, const Seperator* sep)
}
else {
bot_iter->SetCommandedSpell(true);
bot_iter->SetHasLoS(BotSpellTypeRequiresLoS(spell_type) ? bot_iter->DoLosChecks(new_tar) : true);
if (bot_iter->AICastSpell(new_tar, 100, spell_type, sub_target_type, sub_type)) {
if (!first_found) {
first_found = bot_iter;
+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());
Mob* tar = c->GetTarget();
bool is_success = false;
for (auto my_bot : sbl) {
if (my_bot->BotPassiveCheck()) {
if (!my_bot->ValidStateCheck(c)) {
continue;
}
@@ -68,6 +69,11 @@ void bot_command_click_item(Client* c, const Seperator* sep)
continue;
}
is_success = true;
my_bot->TryItemClick(slot_id);
}
if (!is_success) {
c->Message(Chat::Yellow, "None of your bots are capable of doing that currently.");
}
}
+5 -1
View File
@@ -197,7 +197,11 @@ void bot_command_depart(Client* c, const Seperator* sep)
bot_iter->SetCommandedSpell(true);
if (!IsValidSpellAndLoS(itr->SpellId, bot_iter->HasLoS())) {
if (!IsValidSpell(itr->SpellId)) {
continue;
}
if (BotRequiresLoSToCast(BotSpellTypes::Teleport, itr->SpellId) && !bot_iter->HasLoS()) {
continue;
}
+1 -1
View File
@@ -18,7 +18,7 @@ void bot_command_precombat(Client* c, const Seperator* sep)
return;
}
if (!c->DoLosChecks(c->GetTarget())) {
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(c->GetTarget())) {
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
return;
+31 -62
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")) {
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]);
return;
}
@@ -40,7 +40,7 @@ void bot_command_pull(Client *c, const Seperator *sep)
if (
!target_mob ||
target_mob == c ||
target_mob->IsOfClientBotMerc() ||
!c->IsAttackAllowed(target_mob)
) {
c->Message(Chat::White, "Your current target is not attackable!");
@@ -48,19 +48,23 @@ void bot_command_pull(Client *c, const Seperator *sep)
return;
}
if (!c->DoLosChecks(target_mob)) {
if (RuleB(Bots, BotsRequireLoS) && !c->DoLosChecks(target_mob)) {
c->Message(Chat::Red, "You must have Line of Sight to use this command.");
return;
}
if (target_mob->IsNPC() && target_mob->GetHateList().size()) {
c->Message(Chat::White, "Your current target is already engaged!");
return;
}
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) {
if (!bot_iter->ValidStateCheck(c)) {
@@ -72,72 +76,37 @@ void bot_command_pull(Client *c, const Seperator *sep)
case Class::Monk:
case Class::Bard:
case Class::Ranger:
bot_puller = bot_iter;
break;
case Class::Warrior:
case Class::ShadowKnight:
case Class::Paladin:
case Class::Berserker:
case Class::Beastlord:
if (!bot_puller) {
bot_iter->SetPullFlag();
bot_puller = bot_iter;
continue;
}
switch (bot_puller->GetClass()) {
case Class::Druid:
case Class::Shaman:
case Class::Cleric:
case Class::Wizard:
case Class::Necromancer:
case Class::Magician:
case Class::Enchanter:
bot_puller = bot_iter;
default:
continue;
}
continue;
case Class::Druid:
case Class::Shaman:
case Class::Cleric:
if (!bot_puller) {
bot_puller = bot_iter;
continue;
}
switch (bot_puller->GetClass()) {
case Class::Wizard:
case Class::Necromancer:
case Class::Magician:
case Class::Enchanter:
bot_puller = bot_iter;
default:
continue;
}
continue;
case Class::Wizard:
case Class::Necromancer:
case Class::Magician:
case Class::Enchanter:
if (!bot_puller) {
bot_puller = bot_iter;
}
continue;
return;
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) {
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());
for (auto bot_iter : sbl) {
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);
}
+3 -3
View File
@@ -182,7 +182,7 @@ bool BotDatabase::LoadBotSpellCastingChances()
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 (
bot_name.empty() ||
@@ -207,7 +207,7 @@ bool BotDatabase::QueryBotCount(const uint32 owner_id, int class_id, uint32& bot
bot_count = BotDataRepository::Count(
database,
fmt::format(
"`owner_id` = {}",
"`owner_id` = {} AND `name` NOT LIKE '%-deleted-%'",
owner_id
)
);
@@ -216,7 +216,7 @@ bool BotDatabase::QueryBotCount(const uint32 owner_id, int class_id, uint32& bot
bot_class_count = BotDataRepository::Count(
database,
fmt::format(
"`owner_id` = {} AND `class` = {}",
"`owner_id` = {} AND `class` = {} AND `name` NOT LIKE '%-deleted-%'",
owner_id,
class_id
)
+1 -1
View File
@@ -48,7 +48,7 @@ public:
/* 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 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/timer.h"
#include "mob.h"
#include <sstream>
@@ -67,7 +68,7 @@ struct BotSpellSetting {
struct BotSpells {
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
uint32 time_cancast; // when we can cast this spell next
int32 recast_delay;
@@ -85,7 +86,7 @@ struct BotSpells {
struct BotSpells_wIndex {
uint32 index; //index of AIBot_spells
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
uint32 time_cancast; // when we can cast this spell next
int32 recast_delay;
@@ -151,4 +152,39 @@ struct BotSpellTypesByClass {
std::string description;
};
struct CombatRangeInput {
Mob* target;
float target_distance;
bool stop_melee_level;
const EQ::ItemInstance* p_item;
const EQ::ItemInstance* s_item;
};
struct CombatRangeOutput {
bool at_combat_range = false;
float melee_distance_min = 0.0f;
float melee_distance = 0.0f;
float melee_distance_max = 0.0f;
};
struct CombatPositioningInput {
Mob* tar;
bool stop_melee_level;
float tar_distance;
float melee_distance_min;
float melee_distance;
float melee_distance_max;
bool behind_mob;
bool front_mob;
};
struct FindPositionInput {
Mob* tar;
float distance_min;
float distance_max;
bool behind_only;
bool front_only;
bool bypass_los;
};
#endif // BOT_STRUCTS
+90 -72
View File
@@ -41,7 +41,10 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
return false;
}
if (chance < 100 && zone->random.Int(0, 100) > chance) {
if (
!IsCommandedSpell() &&
zone->random.Int(0, 100) > chance
) {
return false;
}
@@ -61,10 +64,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
bot_spell.SpellIndex = 0;
bot_spell.ManaCost = 0;
if (BotSpellTypeRequiresLoS(spell_type) && tar != this) {
SetHasLoS(DoLosChecks(tar));
}
else {
if (!BotSpellTypeRequiresLoS(spell_type) || tar == this) {
SetHasLoS(true);
}
@@ -218,8 +218,11 @@ bool Bot::AICastSpell(Mob* tar, uint8 chance, uint16 spell_type, uint16 sub_targ
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, (IsAEBotSpellType(spell_type) || sub_target_type == CommandedSubTypes::AETarget), sub_target_type, sub_type);
for (const auto& s : bot_spell_list) {
if (!IsValidSpell(s.SpellId)) {
continue;
}
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
if (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) {
continue;
}
@@ -273,7 +276,11 @@ bool Bot::BotCastMez(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type));
for (const auto& s : bot_spell_list) {
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
if (!IsValidSpell(s.SpellId)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, s.SpellId) && !HasLoS()) {
continue;
}
@@ -281,7 +288,7 @@ bool Bot::BotCastMez(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
Mob* add_mob = GetFirstIncomingMobToMez(this, s.SpellId, spell_type, IsAEBotSpellType(spell_type));
if (!add_mob) {
return false;
continue;
}
tar = add_mob;
@@ -327,7 +334,11 @@ bool Bot::BotCastCure(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spe
bot_spell = GetBestBotSpellForCure(this, tar, spell_type);
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
if (!IsValidSpell(bot_spell.SpellId)) {
return false;
}
if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) {
return false;
}
@@ -397,7 +408,11 @@ bool Bot::BotCastPet(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
bot_spell = GetFirstBotSpellBySpellType(this, spell_type);
}
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
if (!IsValidSpell(bot_spell.SpellId)) {
return false;
}
if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) {
return false;
}
@@ -418,6 +433,10 @@ bool Bot::BotCastPet(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spel
}
bool Bot::BotCastNuke(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spell_type) {
if (BotRequiresLoSToCast(spell_type, bot_spell.SpellId) && !HasLoS()) {
return false;
}
if (spell_type == BotSpellTypes::Stun || spell_type == BotSpellTypes::AEStun) {
uint8 stun_chance = (tar->IsCasting() ? RuleI(Bots, StunCastChanceIfCasting) : RuleI(Bots, StunCastChanceNormal));
@@ -434,25 +453,25 @@ bool Bot::BotCastNuke(Mob* tar, uint8 bot_class, BotSpell& bot_spell, uint16 spe
) {
bot_spell = GetBestBotSpellForStunByTargetType(this, ST_TargetOptional, spell_type, IsAEBotSpellType(spell_type), tar);
}
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
if (!IsValidSpell(bot_spell.SpellId)) {
return false;
}
}
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
if (!IsValidSpell(bot_spell.SpellId)) {
bot_spell = GetBestBotSpellForNukeByBodyType(this, tar->GetBodyType(), spell_type, IsAEBotSpellType(spell_type), tar);
}
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS()) && spell_type == BotSpellTypes::Nuke && bot_class == Class::Wizard) {
if (spell_type == BotSpellTypes::Nuke && bot_class == Class::Wizard && !IsValidSpell(bot_spell.SpellId)) {
bot_spell = GetBestBotWizardNukeSpellByTargetResists(this, tar, spell_type);
}
if (!IsValidSpellAndLoS(bot_spell.SpellId, HasLoS())) {
if (!IsValidSpell(bot_spell.SpellId)) {
std::vector<BotSpell_wPriority> bot_spell_list = GetPrioritizedBotSpellsBySpellType(this, spell_type, tar, IsAEBotSpellType(spell_type));
for (const auto& s : bot_spell_list) {
if (!IsValidSpellAndLoS(s.SpellId, HasLoS())) {
if (!IsValidSpell(s.SpellId)) {
continue;
}
@@ -562,7 +581,9 @@ bool Bot::AIDoSpellCast(int32 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
// Allow bots to cast buff spells even if they are out of mana
if (
RuleB(Bots, FinishBuffing) &&
manaCost > hasMana && AIBot_spells[i].type == BotSpellTypes::Buff
manaCost > hasMana &&
!IsEngaged() &&
IsBotBuffSpellType(AIBot_spells[i].type)
) {
SetMana(manaCost);
}
@@ -908,7 +929,11 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffect(Bot* caster, uint16 spell_ty
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -946,7 +971,11 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* caster, ui
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -987,7 +1016,11 @@ std::list<BotSpell> Bot::GetBotSpellsBySpellType(Bot* caster, uint16 spell_type)
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -1016,7 +1049,11 @@ std::vector<BotSpell_wPriority> Bot::GetPrioritizedBotSpellsBySpellType(Bot* cas
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -1103,7 +1140,11 @@ BotSpell Bot::GetFirstBotSpellBySpellType(Bot* caster, uint16 spell_type) {
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -1135,7 +1176,6 @@ BotSpell Bot::GetBestBotSpellForVeryFastHeal(Bot* caster, Mob* tar, uint16 spell
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
for (auto bot_spell_list_itr : bot_spell_list) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (
IsVeryFastHealSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) {
result.SpellId = bot_spell_list_itr.SpellId;
@@ -1161,7 +1201,6 @@ BotSpell Bot::GetBestBotSpellForFastHeal(Bot* caster, Mob* tar, uint16 spell_typ
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
for (auto bot_spell_list_itr : bot_spell_list) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsFastHealSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) {
result.SpellId = bot_spell_list_itr.SpellId;
result.SpellIndex = bot_spell_list_itr.SpellIndex;
@@ -1186,7 +1225,6 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* caster, Mob* tar, uint16 spell
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_HealOverTime);
for (auto bot_spell_list_itr : bot_spell_list) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsHealOverTimeSpell(bot_spell_list_itr.SpellId) && caster->CastChecks(bot_spell_list_itr.SpellId, tar, spell_type)) {
result.SpellId = bot_spell_list_itr.SpellId;
result.SpellIndex = bot_spell_list_itr.SpellIndex;
@@ -1243,7 +1281,6 @@ BotSpell Bot::GetBestBotSpellForRegularSingleTargetHeal(Bot* caster, Mob* tar, u
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsRegularSingleTargetHealSpell(bot_spell_list_itr->SpellId) && caster->CastChecks(bot_spell_list_itr->SpellId, tar, spell_type)) {
result.SpellId = bot_spell_list_itr->SpellId;
result.SpellIndex = bot_spell_list_itr->SpellIndex;
@@ -1268,7 +1305,6 @@ BotSpell Bot::GetFirstBotSpellForSingleTargetHeal(Bot* caster, Mob* tar, uint16
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_CurrentHP);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsRegularSingleTargetHealSpell(bot_spell_list_itr->SpellId) && caster->CastChecks(bot_spell_list_itr->SpellId, tar, spell_type)) {
result.SpellId = bot_spell_list_itr->SpellId;
result.SpellIndex = bot_spell_list_itr->SpellIndex;
@@ -1298,7 +1334,6 @@ BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* caster, Mob* tar, uint16 spell_ty
int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsRegularGroupHealSpell(bot_spell_list_itr->SpellId)) {
uint16 spell_id = bot_spell_list_itr->SpellId;
@@ -1337,7 +1372,6 @@ BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* caster, Mob* tar, uint16
int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsGroupHealOverTimeSpell(bot_spell_list_itr->SpellId)) {
uint16 spell_id = bot_spell_list_itr->SpellId;
@@ -1376,7 +1410,6 @@ BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* caster, Mob* tar, uint16
int required_count = caster->GetSpellTypeAEOrGroupTargetCount(spell_type);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsGroupCompleteHealSpell(bot_spell_list_itr->SpellId)) {
uint16 spell_id = bot_spell_list_itr->SpellId;
@@ -1410,7 +1443,6 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* caster, uint16 spell_type) {
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Mez);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (
IsMesmerizeSpell(bot_spell_list_itr->SpellId) &&
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)
@@ -1427,17 +1459,16 @@ BotSpell Bot::GetBestBotSpellForMez(Bot* caster, uint16 spell_type) {
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;
if (caster && caster->GetOwner()) {
int spell_range = caster->GetActSpellRange(spell_id, spells[spell_id].range);
int spell_ae_range = caster->GetAOERange(spell_id);
int buff_count = 0;
bool is_pbae_spell = IsPBAESpell(spell_id);
NPC* npc = nullptr;
for (auto& close_mob : caster->m_close_mobs) {
buff_count = 0;
npc = close_mob.second->CastToNPC();
if (!npc) {
@@ -1448,29 +1479,29 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
continue;
}
if (is_pbae_spell) {
if (spell_ae_range < Distance(caster->GetPosition(), npc->GetPosition())) {
continue;
}
}
else {
if (spell_range < Distance(caster->GetPosition(), npc->GetPosition())) {
continue;
}
}
if (AE) {
int target_count = 0;
for (auto& close_mob : caster->m_close_mobs) {
Mob* m = close_mob.second;
if (npc == m) {
continue;
}
if (!caster->IsValidMezTarget(caster->GetOwner(), m, spell_id)) {
continue;
}
if (IsPBAESpell(spell_id)) {
if (spell_ae_range < Distance(caster->GetPosition(), m->GetPosition())) {
continue;
}
}
else {
if (spell_range < Distance(m->GetPosition(), npc->GetPosition())) {
continue;
}
if (spell_ae_range < Distance(npc->GetPosition(), m->GetPosition())) {
continue;
}
if (caster->CastChecks(spell_id, m, spell_type, true, true)) {
@@ -1486,11 +1517,6 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
continue;
}
if (zone->random.Int(1, 100) < RuleI(Bots, AEMezChance)) {
caster->SetSpellTypeRecastTimer(spell_type, RuleI(Bots, MezFailDelay));
return result;
}
result = npc;
}
else {
@@ -1502,18 +1528,10 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
continue;
}
if (zone->random.Int(1, 100) < RuleI(Bots, MezChance)) {
caster->SetSpellTypeRecastTimer(spell_type, RuleI(Bots, MezAEFailDelay));
return result;
}
result = npc;
}
if (result) {
caster->SetHasLoS(true);
return result;
}
}
@@ -1534,7 +1552,6 @@ BotSpell Bot::GetBestBotMagicianPetSpell(Bot* caster, uint16 spell_type) {
std::string pet_type = GetBotMagicianPetType(caster);
for(std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (
IsSummonPetSpell(bot_spell_list_itr->SpellId) &&
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId) &&
@@ -1716,7 +1733,6 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType ta
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffectAndTargetType(caster, spell_type, SE_CurrentHP, target_type);
for(std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsPureNukeSpell(bot_spell_list_itr->SpellId) || IsDamageSpell(bot_spell_list_itr->SpellId)) {
if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) {
continue;
@@ -1729,7 +1745,6 @@ BotSpell Bot::GetBestBotSpellForNukeByTargetType(Bot* caster, SpellTargetType ta
continue;
}
if (
caster->IsCommandedSpell() ||
!AE ||
@@ -1766,7 +1781,6 @@ BotSpell Bot::GetBestBotSpellForStunByTargetType(Bot* caster, SpellTargetType ta
for(std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr)
{
// Assuming all the spells have been loaded into this list by level and in descending order
if (IsStunSpell(bot_spell_list_itr->SpellId)) {
if (!AE && IsAnyAESpell(bot_spell_list_itr->SpellId) && !IsGroupSpell(bot_spell_list_itr->SpellId)) {
continue;
@@ -1830,7 +1844,6 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target,
bool spell_selected = false;
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (!caster->IsValidSpellRange(bot_spell_list_itr->SpellId, target)) {
continue;
}
@@ -1887,8 +1900,6 @@ BotSpell Bot::GetBestBotWizardNukeSpellByTargetResists(Bot* caster, Mob* target,
if (!spell_selected) {
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)) {
if (caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)) {
spell_selected = true;
@@ -1926,7 +1937,11 @@ BotSpell Bot::GetDebuffBotSpell(Bot* caster, Mob *tar, uint16 spell_type) {
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -1972,7 +1987,11 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* caster, Mob *tar, uint16 spell
const std::vector<BotSpells_wIndex>& bot_spell_list = caster->BotGetSpellsByType(spell_type);
for (int i = bot_spell_list.size() - 1; i >= 0; i--) {
if (!IsValidSpellAndLoS(bot_spell_list[i].spellid, caster->HasLoS())) {
if (!IsValidSpell(bot_spell_list[i].spellid)) {
continue;
}
if (BotRequiresLoSToCast(spell_type, bot_spell_list[i].spellid) && !caster->HasLoS()) {
continue;
}
@@ -2091,7 +2110,6 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spell_type)
case BotSpellTypes::AERains:
case BotSpellTypes::AEStun:
case BotSpellTypes::AESnare:
case BotSpellTypes::AEMez:
case BotSpellTypes::AESlow:
case BotSpellTypes::AEDebuff:
case BotSpellTypes::AEFear:
@@ -2157,6 +2175,8 @@ uint8 Bot::GetChanceToCastBySpellType(uint16 spell_type)
case BotSpellTypes::PetVeryFastHeals:
case BotSpellTypes::PetHoTHeals:
return RuleI(Bots, PercentChanceToCastHeal);
case BotSpellTypes::AEMez:
return RuleI(Bots, PercentChanceToCastAEMez);
default:
return RuleI(Bots, PercentChanceToCastOtherType);
}
@@ -2821,7 +2841,6 @@ BotSpell Bot::GetBestBotSpellForRez(Bot* caster, Mob* target, uint16 spell_type)
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Revive);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (
IsResurrectSpell(bot_spell_list_itr->SpellId) &&
caster->CheckSpellRecastTimer(bot_spell_list_itr->SpellId)
@@ -2849,7 +2868,6 @@ BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_typ
std::list<BotSpell> bot_spell_list = GetBotSpellsForSpellEffect(caster, spell_type, SE_Charm);
for (std::list<BotSpell>::iterator bot_spell_list_itr = bot_spell_list.begin(); bot_spell_list_itr != bot_spell_list.end(); ++bot_spell_list_itr) {
// Assuming all the spells have been loaded into this list by level and in descending order
if (
IsCharmSpell(bot_spell_list_itr->SpellId) &&
caster->CastChecks(bot_spell_list_itr->SpellId, target, spell_type)
+57
View File
@@ -0,0 +1,57 @@
#include "../../zone.h"
inline void RunTest(const std::string &test_name, const std::string &expected, const std::string &actual)
{
if (expected == actual) {
std::cout << "[✅] " << test_name << " PASSED\n";
} else {
std::cerr << "[❌] " << test_name << " FAILED\n";
std::cerr << " 📌 Expected: " << expected << "\n";
std::cerr << " ❌ Got: " << actual << "\n";
std::exit(1);
}
}
inline void RunTest(const std::string &test_name, bool expected, bool actual)
{
if (expected == actual) {
std::cout << "[✅] " << test_name << " PASSED\n";
}
else {
std::cerr << "[❌] " << test_name << " FAILED\n";
std::cerr << " 📌 Expected: " << (expected ? "true" : "false") << "\n";
std::cerr << " ❌ Got: " << (actual ? "true" : "false") << "\n";
std::exit(1);
}
}
inline void RunTest(const std::string &test_name, int expected, int actual)
{
if (expected == actual) {
std::cout << "[✅] " << test_name << " PASSED\n";
}
else {
std::cerr << "[❌] " << test_name << " FAILED\n";
std::cerr << " 📌 Expected: " << expected << "\n";
std::cerr << " ❌ Got: " << actual << "\n";
std::exit(1);
}
}
extern Zone *zone;
inline void SetupZone(std::string zone_short_name, uint32 instance_id = 0) {
LogSys.SilenceConsoleLogging();
LogSys.log_settings[Logs::ZoneState].log_to_console = std::getenv("DEBUG") ? 3 : 0;
LogSys.log_settings[Logs::Info].log_to_console = std::getenv("DEBUG") ? 3 : 0;
LogSys.log_settings[Logs::Spawns].log_to_console = std::getenv("DEBUG") ? 3 : 0;
// boot shell zone for testing
Zone::Bootup(ZoneID(zone_short_name), 0, false);
zone->StopShutdownTimer();
entity_list.Process();
entity_list.MobProcess();
LogSys.EnableConsoleLogging();
}
@@ -1,25 +1,13 @@
#include "../../common/http/httplib.h"
#include "../../common/eqemu_logsys.h"
#include "../../common/platform.h"
#include "../zone.h"
#include "../client.h"
#include "../../zone.h"
#include "../../client.h"
#include "../../common/net/eqstream.h"
extern Zone *zone;
void RunTest(const std::string &test_name, const std::string &expected, const std::string &actual)
{
if (expected == actual) {
std::cout << "[✅] " << test_name << " PASSED\n";
} else {
std::cerr << "[❌] " << test_name << " FAILED\n";
std::cerr << " 📌 Expected: " << expected << "\n";
std::cerr << " ❌ Got: " << actual << "\n";
std::exit(1);
}
}
void ZoneCLI::DataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
void ZoneCLI::TestDataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
{
if (cmd[{"-h", "--help"}]) {
return;
@@ -1,8 +1,8 @@
#include "../../common/http/httplib.h"
#include "../../common/eqemu_logsys.h"
#include "../../common/platform.h"
#include "../zone.h"
#include "../client.h"
#include "../../zone.h"
#include "../../client.h"
#include "../../common/net/eqstream.h"
#include "../../common/json/json.hpp"
@@ -36,19 +36,6 @@ struct TestCase {
bool handin_check_result;
};
void RunTest(const std::string &test_name, bool expected, bool actual)
{
if (expected == actual) {
std::cout << "[✅] " << test_name << " PASSED\n";
}
else {
std::cerr << "[❌] " << test_name << " FAILED\n";
std::cerr << " 📌 Expected: " << (expected ? "true" : "false") << "\n";
std::cerr << " ❌ Got: " << (actual ? "true" : "false") << "\n";
std::exit(1);
}
}
void RunSerializedTest(const std::string &test_name, const std::string &expected, const std::string &actual)
{
if (expected == actual) {
@@ -75,7 +62,7 @@ std::string SerializeHandin(const std::map<std::string, uint32> &items, const Ha
return j.dump();
}
void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
void ZoneCLI::TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
{
if (cmd[{"-h", "--help"}]) {
return;
@@ -1,13 +1,13 @@
#include "../../common/http/httplib.h"
#include "../../common/eqemu_logsys.h"
#include "../../common/platform.h"
#include "../zone.h"
#include "../client.h"
#include "../../zone.h"
#include "../../client.h"
#include "../../common/net/eqstream.h"
extern Zone *zone;
void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description)
void ZoneCLI::TestNpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std::string &description)
{
if (cmd[{"-h", "--help"}]) {
return;
File diff suppressed because it is too large Load Diff
+62 -2
View File
@@ -882,9 +882,13 @@ void Client::SendZoneInPackets()
//SendGuildMembers();
SendGuildURL();
SendGuildChannel();
SendGuildLFGuildStatus();
if (RuleB(Guild, EnableLFGuild)) {
SendGuildLFGuildStatus();
}
}
if (RuleB(Guild, EnableLFGuild)) {
SendLFGuildStatus();
}
SendLFGuildStatus();
//No idea why live sends this if even were not in a guild
SendGuildMOTD();
@@ -991,6 +995,8 @@ bool Client::Save(uint8 iCommitNow) {
if(!ClientDataLoaded())
return false;
BenchTimer timer;
/* Wrote current basics to PP for saves */
if (!m_lock_save_position) {
m_pp.x = m_Position.x;
@@ -1101,6 +1107,8 @@ bool Client::Save(uint8 iCommitNow) {
database.botdb.SaveBotSettings(this);
}
LogInfo("Save for [{}] took [{}]", GetCleanName(), timer.elapsed());
return true;
}
@@ -1205,6 +1213,58 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s
LogDebug("Client::ChannelMessageReceived() Channel:[{}] message:[{}]", chan_num, message);
if (RuleB(Chat, AlwaysCaptureCommandText)) {
if (message[0] == COMMAND_CHAR) {
if (command_dispatch(this, message, false) == -2) {
if (parse->PlayerHasQuestSub(EVENT_COMMAND)) {
int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0);
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Command '%s' not recognized.", message);
}
}
else if (parse->PlayerHasQuestSub(EVENT_SAY)) {
int i = parse->EventPlayer(EVENT_SAY, this, message, 0);
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Command '%s' not recognized.", message);
}
}
else {
if (!RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Command '%s' not recognized.", message);
}
}
}
return;
}
if (message[0] == BOT_COMMAND_CHAR) {
if (RuleB(Bots, Enabled)) {
if (bot_command_dispatch(this, message) == -2) {
if (parse->PlayerHasQuestSub(EVENT_BOT_COMMAND)) {
int i = parse->EventPlayer(EVENT_BOT_COMMAND, this, message, 0);
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Bot command '%s' not recognized.", message);
}
}
else if (parse->PlayerHasQuestSub(EVENT_SAY)) {
int i = parse->EventPlayer(EVENT_SAY, this, message, 0);
if (i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Bot command '%s' not recognized.", message);
}
}
else {
if (!RuleB(Chat, SuppressCommandErrors)) {
Message(Chat::Red, "Bot command '%s' not recognized.", message);
}
}
}
} else {
Message(Chat::Red, "Bots are disabled on this server.");
}
return;
}
}
if (targetname == nullptr) {
targetname = (!GetTarget()) ? "" : GetTarget()->GetName();
}

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