Compare commits

...

78 Commits

Author SHA1 Message Date
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
catapultam-habeo 213fe6a9e9 [Feature] Implement /changename & related script bindings. Clean up #set name (#4770)
* initial work, need to clean up gm commands still

* cleaned up command, works without kicking char select now

* remove thj-specific methods

* add script hooks

* actually clear flag

* rework questmgr::rename

* remove unnecessary logging

* revert

* added missing binding to perl api and updated some text

* don't return a value

* Fix some bad argument types.

* adjust case

* alpha order

* refactor some old string stuff

* don't quote integers, bob

---------

Co-authored-by: Zimp <zimp@zenryo.xyz>
Co-authored-by: Chris Miles <akkadius1@gmail.com>
2025-03-19 21:00:45 -05:00
Alex King be6a5d5f50 [Quest API] Add Support for NPC ID and NPC Name Specificity (#4781)
* [Quest API] Add Support for NPC ID and NPC Name Specificity

* Update quest_parser_collection.cpp

* Update quest_parser_collection.cpp

* Update quest_parser_collection.cpp

* Update quest_parser_collection.cpp

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
2025-03-19 17:55:00 -05:00
nytmyr 1af29bd7b1 [Bots] Fix IsValidSpellTypeBySpellID to account for all types (#4764)
* [Bots] Fix IsValidSpellTypeBySpellID to account for all types

* Formatting
2025-03-19 17:43:15 -05:00
zimp-wow ef945e6e99 [Fix] Fix zone crash when attempting to add a disappearing client to hate list. (#4782) 2025-03-19 16:26:54 -05:00
Chris Miles 9528c1e7fc [Fix] Zone State Entity Variable Load Pre-Spawn (#4785)
* [Fix] Zone state ent variable pre-spawn

* Update zone_save_state.cpp

* Update zone_save_state.cpp

* Update spawn2.cpp

* Update zone_save_state.cpp

* Update zone_save_state.cpp
2025-03-19 16:21:36 -05:00
Chris Miles fd6e5f465d [Fix] Zone State Position Fix (#4784) 2025-03-19 16:17:25 -05:00
nytmyr d00125abe1 [Bots] Charmed Pets were breaking Mob respawns (#4780)
- When bots would charm a pet, once they zoned or camped the pet would poof and not trigger a respawn so the NPC which was charmed would never respawn until the zone was shut down or server restarted.
2025-03-16 19:12:48 -04:00
Akkadius 5d69235a4c [Release] 23.3.4 2025-03-14 13:09:53 -05:00
Mitch Freeman e93785f885 [Fix] Add check for simultaneous direct vendor and parcel Trader/Buyer Purchase (#4778) 2025-03-13 22:30:00 -05:00
Chris Miles 3c2545cfaf [Release] 23.3.3 (#4777) 2025-03-13 17:06:52 -05:00
Chris Miles 8d1a9efac9 [Zone] Zone State Improvements Part 3 (#4773)
* [Zone State] Additional improvements

* Return early

* Update zone_save_state.cpp

* Push

* Push

* Update zone.cpp

* Update zone_save_state.cpp

* Equip items that were dynamically added on restore

* IsZoneStateValid helper

* ZoneStateSpawnsRepository::PurgeInvalidZoneStates

* Add Zone:StateSaveClearDays and PurgeOldZoneStates

* spawn2 / unique_spawn block when restored from zone state

* One time purge

* Update zone_state_spawns_repository.h

* Update npc.cpp

* Update npc.cpp

* test

* ORDER BY spawn2_id

* Stuff

* Restored corpses shouldn't trigger events

* Fix weird edge case
2025-03-13 17:00:30 -05:00
Mitch Freeman f6b18fb003 [Fix] Update GuildBank to correctly handle items with charges equal to zero (#4774) 2025-03-12 21:57:29 -04:00
dependabot[bot] 00e77f190c Bump golang.org/x/net in /utils/scripts/build/should-release (#4775)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.33.0...v0.36.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 20:57:06 -05:00
Chris Miles 9cb72a6ba7 [Networking] Fix "port in use" error (#4772) 2025-03-12 00:52:17 -05:00
Chris Miles 8203c034bf [Database] Add indexes for data_buckets and zone_state_spawns (#4771)
* [Database] Add indexes for data_buckets and zone_state_spawns

* Update database_update_manifest.cpp

* Update database_update_manifest.cpp

* Update package.json
2025-03-11 03:29:26 -05:00
Akkadius 33ae51f56f [Release] 23.3.1 2025-03-11 01:38:59 -05:00
Chris Miles 30c39194a3 [Zone] Zone State Improvements (Continued) (#4768)
* [Zone] Zone State Improvements (Continued)

* Ignore a few events when we resume from suspend

* Add is_zone field

* Update database_update_manifest.cpp

* Update database_update_manifest.cpp

* Update database_update_manifest.cpp

* Update zone_save_state.cpp

* Update zone_save_state.cpp

* Add Zone Variables

* Update methods

* Update zone_save_state.cpp

* Update zone_save_state.cpp

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
2025-03-11 01:14:09 -05:00
hg 051ce3736f [DynamicZones] Bulk request dz member statuses on zone boot (#4769)
When dynamic zones are cached on zone boot each dz requests member
statuses from world separately. This causes a lot of network traffic
between world and booted zones when there are a lot of active dzs.

This changes it to make a single request to world on zone boot and a
single bulk reply back.
2025-03-11 01:13:29 -05:00
Mitch Freeman 84708edccf Update client_evolving_items.cpp (#4767) 2025-03-09 12:20:37 -04:00
Chris Miles da4e9ab95b [Release] 23.3.0 (#4766) 2025-03-08 03:18:40 -06:00
Chris Miles a8fea95eab [Zone] State Save Improvements (#4765)
* [Zone] State saving improvements

* Make sure we load spawn enabled off of the state row

* Update npc.h

* Update spawn2.cpp

* Update database_instances.cpp

* Update database_instances.cpp
2025-03-08 03:15:42 -06:00
Mitch Freeman 53610c2f0f [Feature] Add Rule for dealing with augments when an item evolves (#4758) 2025-03-07 23:00:11 -06:00
catapultam-habeo 9f10c12874 [Bug Fix] Correct incorrectly calculated stat caps with Heroic Stats (#4760)
* fix incorrectly caclulcated stat caps

* fix typos and formatting
2025-03-07 22:59:47 -06:00
catapultam-habeo a0634adb3c [Feature] Allow assigning Helm Texture independently of Body Texture for Horses (#4759) 2025-03-07 22:59:12 -06:00
zimp-wow a2ed6be1f5 [Bug Fix] Zero out currentnpcid whenever spawn is reset. (#4763) 2025-03-07 22:38:23 -06:00
nytmyr c33ac40567 [Cleanup] Fix typo in GM tradeskill combine message (#4762) 2025-03-07 22:33:43 -06:00
Mitch Freeman 9ee095b354 [Fix] Add crash checks for certain PlayerEventLogs (#4761) 2025-03-07 16:17:36 -06:00
Alex King 7ab32af4dc [Rules] Fix EvolvingItems:PercentOfRaidExperience Description (#4757)
- Fixes an issue where the description was inaccurate, described [here](https://discord.com/channels/212663220849213441/557677602706423982/1347293634453835899).
2025-03-06 23:49:36 -05:00
Chris Miles 0c301419c2 [Zone] Make zone controller less likely to be visible, immune to all forms of combat (#4750)
* [Zone] Make zone controller less likely to be visible, immune to all forms of combat

* Exclude zone controller from scanning
2025-03-06 17:08:08 -05:00
nytmyr d6a21be25e [Bots] Fix taunting bots positioning (#4754)
* [Bots] Fix taunting bots positioning

- Fixes taunting bots liking to hug their target on certain models or chosen positions.
- Makes bots have a more realistic combat range in comparison to players.
- Removed unnecessary rules and checks for melee distance.

* Update ruletypes.h
2025-03-06 17:07:38 -05:00
nytmyr 1d4ba082ad [Bots] Move commanded spell map to zone (#4755)
- Moves the mapping of commanded spell min levels to zone rather than to each individual bot.
- Adds support for sub spell types.
2025-03-06 17:05:36 -05:00
nytmyr 94553501ba [Bots] Fix buffs not overwriting lesser buffs (#4756) 2025-03-06 17:03:50 -05:00
nytmyr da824d5178 [Crash] Bot aura crash fix (#4752)
- Something between the latest release caused this crash to appear, unsure of this exact cause.
- Prevents bots from being sent a spawn packet for Auras.
- Removes the bot's auras on Depop
2025-03-06 01:12:24 -05:00
Mitch Freeman 5a1df38900 [Fix] Parcel Delivery Updates for two edge cases (#4753)
- Properly send an item via parcel that has 0 charges
2025-03-06 01:12:02 -05:00
nytmyr 8cd7148b29 [Pets] Fix renamed pets loading as blank names (#4751) 2025-03-05 14:31:16 -05:00
Akkadius 09e079a45e [Hotfix] Forgot to push up some changes for test output 2025-03-04 17:18:02 -06:00
Chris Miles 4bc881da4b [Tests] Cleanup Hand-in Tests (#4749) 2025-03-04 17:15:27 -06:00
Chris Miles 0615864d51 [Databuckets] Nested Databuckets Protections and Improvements (#4748)
* Check for valid JSON before using it

* Do not allow nested keys to set be set an expiration

* Prevent overwriting of existing object or array

* Nested deletion support

* Update data_bucket.cpp

* Test cases

* More test cases, fix

* Update databuckets.cpp

* Update databuckets.cpp

* Basic databucket tests

* Update databuckets.cpp

* Update databuckets.cpp
2025-03-04 13:16:21 -06:00
Alex King 3638d157b2 [Logging] Convert JSON Error to Data Buckets Logging Category (#4747) 2025-03-03 23:16:47 -06:00
128 changed files with 5214 additions and 2083 deletions
+177
View File
@@ -1,3 +1,180 @@
## [23.4.0] 3/30/2025
### API
* Expose Zoneserver Compile Metadata ([#4815](https://github.com/EQEmu/Server/pull/4815)) @Akkadius 2025-03-29
### Bots
* Charmed Pets were breaking Mob respawns ([#4780](https://github.com/EQEmu/Server/pull/4780)) @nytmyr 2025-03-16
* Enraged positioning ([#4789](https://github.com/EQEmu/Server/pull/4789)) @nytmyr 2025-03-29
* Fix IsValidSpellTypeBySpellID to account for all types ([#4764](https://github.com/EQEmu/Server/pull/4764)) @nytmyr 2025-03-19
* Fix Rule ZonesWithSpawnLimits/ZonesWithForcedSpawnLimits errors ([#4791](https://github.com/EQEmu/Server/pull/4791)) @nytmyr 2025-03-29
* Fix rule Bots:FinishBuffing ([#4788](https://github.com/EQEmu/Server/pull/4788)) @nytmyr 2025-03-29
* Line of Sight and Mez optimizations and cleanup ([#4746](https://github.com/EQEmu/Server/pull/4746)) @nytmyr 2025-03-29
* Prevent bot pets from despawning on #repop ([#4790](https://github.com/EQEmu/Server/pull/4790)) @nytmyr 2025-03-29
### Code
* Control flow defaults missed in recent bot updates ([#4817](https://github.com/EQEmu/Server/pull/4817)) @joligario 2025-03-30
* Remove Extraneous Time Type in ShowZoneData ([#4806](https://github.com/EQEmu/Server/pull/4806)) @Kinglykrab 2025-03-29
* Remove Unused Command Methods ([#4805](https://github.com/EQEmu/Server/pull/4805)) @Kinglykrab 2025-03-29
* UCS Member Count ([#4819](https://github.com/EQEmu/Server/pull/4819)) @joligario 2025-03-30
### Commands
* Add #show zone_variables ([#4812](https://github.com/EQEmu/Server/pull/4812)) @Akkadius 2025-03-29
* Add Instance Support to #zoneshutdown ([#4807](https://github.com/EQEmu/Server/pull/4807)) @Kinglykrab 2025-03-29
### Crash
* Fix Rarer World Crash with Player Event Thread Processor ([#4800](https://github.com/EQEmu/Server/pull/4800)) @Akkadius 2025-03-29
* Fix Repop Race Condition Crash ([#4814](https://github.com/EQEmu/Server/pull/4814)) @Akkadius 2025-03-29
### Database
* Fix Respawn Times Table ([#4802](https://github.com/EQEmu/Server/pull/4802)) @Akkadius 2025-03-29
* Wrap PurgeExpiredInstances in a Transaction ([#4824](https://github.com/EQEmu/Server/pull/4824)) @Akkadius 2025-03-30
### Feature
* Implement /changename & related script bindings. Clean up #set name ([#4770](https://github.com/EQEmu/Server/pull/4770)) @catapultam-habeo 2025-03-20
### Fixes
* AllowFVNoDrop Flag trades ([#4809](https://github.com/EQEmu/Server/pull/4809)) @neckkola 2025-03-27
* Fix Instance Creation Race Condition ([#4803](https://github.com/EQEmu/Server/pull/4803)) @Akkadius 2025-03-29
* Fix zone crash when attempting to add a disappearing client to hate list. ([#4782](https://github.com/EQEmu/Server/pull/4782)) @zimp-wow 2025-03-19
* Globally Reloading Quests when not loaded ([#4813](https://github.com/EQEmu/Server/pull/4813)) @Akkadius 2025-03-29
* Instance DZ Creation ([#4823](https://github.com/EQEmu/Server/pull/4823)) @Akkadius 2025-03-30
* Zone State Entity Variable Load Pre-Spawn ([#4785](https://github.com/EQEmu/Server/pull/4785)) @Akkadius 2025-03-19
* Zone State Position Fix ([#4784](https://github.com/EQEmu/Server/pull/4784)) @Akkadius 2025-03-19
* Zone State Variables Load First ([#4798](https://github.com/EQEmu/Server/pull/4798)) @Akkadius 2025-03-29
* Zone state edge case with 0 hp ([#4787](https://github.com/EQEmu/Server/pull/4787)) @Akkadius 2025-03-29
### Instance
* Clear Respawn Timers on Creation ([#4801](https://github.com/EQEmu/Server/pull/4801)) @Akkadius 2025-03-29
### Instances
* Add `expire_at` Column ([#4820](https://github.com/EQEmu/Server/pull/4820)) @Akkadius 2025-03-30
### Performance
* Add several database indexes ([#4811](https://github.com/EQEmu/Server/pull/4811)) @Akkadius 2025-03-29
* Have World Send Smarter Guild Updates ([#4796](https://github.com/EQEmu/Server/pull/4796)) @Akkadius 2025-03-29
* Improve Character Select DB Performance ([#4799](https://github.com/EQEmu/Server/pull/4799)) @Akkadius 2025-03-29
* Reduce Adventure S2S chatter ([#4793](https://github.com/EQEmu/Server/pull/4793)) @Akkadius 2025-03-29
* Reduce CorpseOwnerOnline S2S Chatter to World ([#4795](https://github.com/EQEmu/Server/pull/4795)) @Akkadius 2025-03-29
* Reduce LFGuild Chatter ([#4794](https://github.com/EQEmu/Server/pull/4794)) @Akkadius 2025-03-29
* Reduce UpdateWho S2S Chatter to World ([#4792](https://github.com/EQEmu/Server/pull/4792)) @Akkadius 2025-03-29
* Send Smarter Emote Packets ([#4818](https://github.com/EQEmu/Server/pull/4818)) @Akkadius 2025-03-30
### Quest API
* Add Support for NPC ID and NPC Name Specificity ([#4781](https://github.com/EQEmu/Server/pull/4781)) @Kinglykrab 2025-03-19
### Reload
* Add Reload for Maps / Navs ([#4816](https://github.com/EQEmu/Server/pull/4816)) @Akkadius 2025-03-29
### Zone
* Zone State Automated Testing and Improvements ([#4808](https://github.com/EQEmu/Server/pull/4808)) @Akkadius 2025-03-30
* Zone State Improvements Part 3 ([#4773](https://github.com/EQEmu/Server/pull/4773)) @Akkadius 2025-03-13
## [23.3.4] 3/14/2025
### Fixes
* Add check for simultaneous direct vendor and parcel Trader/Buyer Purchase ([#4778](https://github.com/EQEmu/Server/pull/4778)) @neckkola 2025-03-14
* Fix for rare circumstance where NPC's would have 0 health on restore @Akkadius
## [23.3.3] 3/13/2025
### Database
* Add indexes for data_buckets and zone_state_spawns ([#4771](https://github.com/EQEmu/Server/pull/4771)) @Akkadius 2025-03-11
### Fixes
* Update GuildBank to correctly handle items with charges equal to zero ([#4774](https://github.com/EQEmu/Server/pull/4774)) @neckkola 2025-03-13
### Networking
* Fix "port in use" error ([#4772](https://github.com/EQEmu/Server/pull/4772)) @Akkadius 2025-03-12
### Zone
* Zone State Improvements Part 3 ([#4773](https://github.com/EQEmu/Server/pull/4773)) @Akkadius 2025-03-13
## [23.3.2] 3/11/2025
### DynamicZones
* Bulk request dz member statuses on zone boot ([#4769](https://github.com/EQEmu/Server/pull/4769)) @hgtw 2025-03-11
### Zone
* Zone State Improvements (Continued) ([#4768](https://github.com/EQEmu/Server/pull/4768)) @Akkadius 2025-03-11
## [23.3.0] 3/8/2025
### Bots
* Fix buffs not overwriting lesser buffs ([#4756](https://github.com/EQEmu/Server/pull/4756)) @nytmyr 2025-03-06
* Fix taunting bots positioning ([#4754](https://github.com/EQEmu/Server/pull/4754)) @nytmyr 2025-03-06
* Move commanded spell map to zone ([#4755](https://github.com/EQEmu/Server/pull/4755)) @nytmyr 2025-03-06
### Code
* Fix typo in GM tradeskill combine message ([#4762](https://github.com/EQEmu/Server/pull/4762)) @nytmyr 2025-03-08
### Crash
* Bot aura crash fix ([#4752](https://github.com/EQEmu/Server/pull/4752)) @nytmyr 2025-03-06
### Databuckets
* Nested Databuckets Protections and Improvements ([#4748](https://github.com/EQEmu/Server/pull/4748)) @Akkadius 2025-03-04
### Feature
* Add Rule for dealing with augments when an item evolves ([#4758](https://github.com/EQEmu/Server/pull/4758)) @neckkola 2025-03-08
* Allow assigning Helm Texture independently of Body Texture for Horses ([#4759](https://github.com/EQEmu/Server/pull/4759)) @catapultam-habeo 2025-03-08
### Fixes
* Add crash checks for certain PlayerEventLogs ([#4761](https://github.com/EQEmu/Server/pull/4761)) @neckkola 2025-03-07
* Correct incorrectly calculated stat caps with Heroic Stats ([#4760](https://github.com/EQEmu/Server/pull/4760)) @catapultam-habeo 2025-03-08
* Fix sigabort crash from invalid JSON @Akkadius 2025-03-03
* Forgot to push up some changes for test output @Akkadius 2025-03-04
* Parcel Delivery Updates for two edge cases ([#4753](https://github.com/EQEmu/Server/pull/4753)) @neckkola 2025-03-06
* Remove one port check in world @Akkadius 2025-03-03
* Zero out currentnpcid whenever spawn is reset. ([#4763](https://github.com/EQEmu/Server/pull/4763)) @zimp-wow 2025-03-08
### Logging
* Convert JSON Error to Data Buckets Logging Category ([#4747](https://github.com/EQEmu/Server/pull/4747)) @Kinglykrab 2025-03-04
### Pets
* Fix renamed pets loading as blank names ([#4751](https://github.com/EQEmu/Server/pull/4751)) @nytmyr 2025-03-05
### Rules
* Fix EvolvingItems:PercentOfRaidExperience Description ([#4757](https://github.com/EQEmu/Server/pull/4757)) @Kinglykrab 2025-03-07
### Tests
* Cleanup Hand-in Tests ([#4749](https://github.com/EQEmu/Server/pull/4749)) @Akkadius 2025-03-04
### Zone
* Make zone controller less likely to be visible, immune to all forms of combat ([#4750](https://github.com/EQEmu/Server/pull/4750)) @Akkadius 2025-03-06
* State Save Improvements ([#4765](https://github.com/EQEmu/Server/pull/4765)) @Akkadius 2025-03-08
## [23.2.0] 3/3/2025
### Crash
+37
View File
@@ -955,6 +955,29 @@ bool Database::UpdateName(const std::string& old_name, const std::string& new_na
return CharacterDataRepository::UpdateOne(*this, e);
}
bool Database::UpdateNameByID(const int character_id, const std::string& new_name)
{
LogInfo("Renaming [{}] to [{}]", character_id, new_name);
auto l = CharacterDataRepository::GetWhere(
*this,
fmt::format(
"`id` = {}",
character_id
)
);
if (l.empty()) {
return false;
}
auto& e = l.front();
e.name = new_name;
return CharacterDataRepository::UpdateOne(*this, e);
}
bool Database::IsNameUsed(const std::string& name)
{
if (RuleB(Bots, Enabled)) {
@@ -982,6 +1005,20 @@ bool Database::IsNameUsed(const std::string& name)
return !character_data.empty();
}
// Players cannot have the same name as a pet vanity name, or memory corruption occurs.
bool Database::IsPetNameUsed(const std::string& name)
{
const auto& pet_name_data = CharacterPetNameRepository::GetWhere(
*this,
fmt::format(
"`name` = '{}'",
Strings::Escape(name)
)
);
return !pet_name_data.empty();
}
uint32 Database::GetServerType()
{
const auto& l = VariablesRepository::GetWhere(*this, "`varname` = 'ServerType' LIMIT 1");
+3
View File
@@ -103,6 +103,7 @@ public:
bool ReserveName(uint32 account_id, const std::string& name);
bool SaveCharacterCreate(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp);
bool UpdateName(const std::string& old_name, const std::string& new_name);
bool UpdateNameByID(const int character_id, const std::string& new_name);
bool CopyCharacter(
const std::string& source_character_name,
const std::string& destination_character_name,
@@ -116,6 +117,7 @@ public:
bool CheckGMIPs(const std::string& login_ip, uint32 account_id);
bool CheckNameFilter(const std::string& name, bool surname = false);
bool IsNameUsed(const std::string& name);
bool IsPetNameUsed(const std::string& name);
uint32 GetAccountIDByChar(const std::string& name, uint32* character_id = 0);
uint32 GetAccountIDByChar(uint32 character_id);
@@ -139,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);
+154 -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 = "",
@@ -6845,7 +6845,7 @@ RENAME TABLE `expedition_lockouts` TO `dynamic_zone_lockouts`;
.condition = "empty",
.match = "",
.sql = R"(
-- Drop old indexes
-- Drop old indexes if exists
DROP INDEX IF EXISTS `keys` ON `data_buckets`;
DROP INDEX IF EXISTS `idx_npc_expires` ON `data_buckets`;
DROP INDEX IF EXISTS `idx_bot_expires` ON `data_buckets`;
@@ -6863,10 +6863,10 @@ ALTER TABLE `data_buckets`
MODIFY COLUMN `npc_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `character_id`,
MODIFY COLUMN `bot_id` int(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `npc_id`;
-- Create optimized unique index with `key` first
-- Create optimized unique index with `key` first
CREATE UNIQUE INDEX `keys` ON data_buckets (`key`, character_id, npc_id, bot_id, account_id, zone_id, instance_id);
-- Create indexes for just instance_id (instance deletion)
-- Create indexes for just instance_id (instance deletion)
CREATE INDEX idx_instance_id ON data_buckets (instance_id);
)",
.content_schema_update = false
@@ -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)",
@@ -6938,6 +6938,155 @@ CREATE TABLE `character_pet_name` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
)",
},
ManifestEntry{
.version = 9310,
.description = "2025_03_7_expand_horse_def.sql",
.check = "SHOW COLUMNS FROM `horses` LIKE 'helmtexture'",
.condition = "missing",
.match = "TINYINT(2)",
.sql = R"(
ALTER TABLE `horses`
ADD COLUMN `helmtexture` TINYINT(2) NOT NULL DEFAULT -1 AFTER `texture`;
)",
.content_schema_update = true
},
ManifestEntry{
.version = 9311,
.description = "2025_03_09_add_zone_state_is_zone_field.sql",
.check = "SHOW COLUMNS FROM `zone_state_spawns` LIKE 'is_zone'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `zone_state_spawns`
ADD COLUMN `is_zone` tinyint(11) NULL DEFAULT 0 AFTER `is_corpse`;
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9312,
.description = "2025_03_11_data_bucket_indexes.sql",
.check = "SHOW INDEX FROM data_buckets",
.condition = "missing",
.match = "idx_zone_instance_expires",
.sql = R"(
DROP INDEX IF EXISTS `idx_zone_instance_expires` ON `data_buckets`;
DROP INDEX IF EXISTS `idx_character_expires` ON `data_buckets`;
DROP INDEX IF EXISTS `idx_bot_expires` ON `data_buckets`;
ALTER TABLE data_buckets ADD INDEX idx_zone_instance_expires (zone_id, instance_id, expires);
ALTER TABLE data_buckets ADD INDEX idx_character_expires (character_id, expires);
ALTER TABLE data_buckets ADD INDEX idx_bot_expires (bot_id, expires);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9313,
.description = "2025_03_11_zone_state_spawns.sql",
.check = "SHOW INDEX FROM zone_state_spawns",
.condition = "missing",
.match = "idx_zone_instance",
.sql = R"(
ALTER TABLE zone_state_spawns ADD INDEX idx_zone_instance (zone_id, instance_id);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9314,
.description = "2025_03_12_zone_state_spawns_one_time_truncate.sql",
.check = "SELECT * FROM db_version WHERE version >= 9314",
.condition = "empty",
.match = "",
.sql = R"(
TRUNCATE TABLE zone_state_spawns;
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9315,
.description = "2025_03_29_character_tribute_index.sql",
.check = "SHOW INDEX FROM character_tribute",
.condition = "missing",
.match = "idx_character_id",
.sql = R"(
ALTER TABLE character_tribute ADD INDEX idx_character_id (character_id);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9316,
.description = "2025_03_29_player_titlesets_index.sql",
.check = "SHOW INDEX FROM player_titlesets",
.condition = "missing",
.match = "idx_char_id",
.sql = R"(
ALTER TABLE player_titlesets ADD INDEX idx_char_id (char_id);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9317,
.description = "2025_03_29_respawn_times_instance_index.sql",
.check = "SHOW INDEX FROM respawn_times",
.condition = "missing",
.match = "idx_instance_id",
.sql = R"(
ALTER TABLE respawn_times ADD INDEX idx_instance_id (instance_id);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9318,
.description = "2025_03_29_zone_state_spawns_instance_index.sql",
.check = "SHOW INDEX FROM zone_state_spawns",
.condition = "missing",
.match = "idx_instance_id",
.sql = R"(
ALTER TABLE zone_state_spawns ADD INDEX idx_instance_id (instance_id);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9319,
.description = "2025_03_29_data_buckets_expires_index.sql",
.check = "SHOW INDEX FROM data_buckets",
.condition = "missing",
.match = "idx_expires",
.sql = R"(
CREATE INDEX idx_expires ON data_buckets (expires);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9320,
.description = "2025_03_23_add_respawn_times_expire_at.sql",
.check = "SHOW COLUMNS FROM `respawn_times` LIKE 'expire_at'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `respawn_times`
ADD COLUMN `expire_at` int(11) UNSIGNED NULL DEFAULT 0 AFTER `duration`;
UPDATE respawn_times set expire_at = `start` + `duration`; -- backfill existing data
CREATE INDEX `idx_expire_at` ON `respawn_times` (`expire_at`);
)",
.content_schema_update = false
},
ManifestEntry{
.version = 9321,
.description = "2025_03_30_instance_list_add_expire_at.sql",
.check = "SHOW COLUMNS FROM `instance_list` LIKE 'expire_at'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `instance_list`
ADD COLUMN `expire_at` bigint(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `duration`;
UPDATE instance_list set expire_at = `start_time` + `duration`; -- backfill existing data
CREATE INDEX `idx_expire_at` ON `instance_list` (`expire_at`);
)",
.content_schema_update = false
},
// -- template; copy/paste this when you need to create a new entry
// ManifestEntry{
// .version = 9228,
+51 -18
View File
@@ -31,7 +31,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "../common/repositories/spawn_condition_values_repository.h"
#include "repositories/spawn2_disabled_repository.h"
#include "repositories/data_buckets_repository.h"
#include "repositories/zone_state_spawns_repository.h"
#include "database.h"
#include <iomanip>
@@ -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;
@@ -480,6 +504,9 @@ void Database::DeleteInstance(uint16 instance_id)
DynamicZonesRepository::DeleteWhere(*this, fmt::format("instance_id = {}", instance_id));
CharacterCorpsesRepository::BuryInstance(*this, instance_id);
DataBucketsRepository::DeleteWhere(*this, fmt::format("instance_id = {}", instance_id));
if (RuleB(Zone, StateSavingOnShutdown)) {
ZoneStateSpawnsRepository::DeleteWhere(*this, fmt::format("`instance_id` = {}", instance_id));
}
}
void Database::FlagInstanceByGroupLeader(uint32 zone_id, int16 version, uint32 character_id, uint32 group_id)
@@ -534,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;
@@ -552,17 +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 ({})", ids));
}
TransactionCommit();
LogInfo("Purged [{}] expired instances", l.size());
}
void Database::SetInstanceDuration(uint16 instance_id, uint32 new_duration)
@@ -574,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);
}
+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
@@ -287,6 +287,8 @@ N(OP_InstillDoubt),
N(OP_InterruptCast),
N(OP_InvokeChangePetName),
N(OP_InvokeChangePetNameImmediate),
N(OP_InvokeNameChangeImmediate),
N(OP_InvokeNameChangeLazy),
N(OP_ItemLinkClick),
N(OP_ItemLinkResponse),
N(OP_ItemLinkText),
+16 -9
View File
@@ -5832,21 +5832,28 @@ struct ChangeSize_Struct
/*16*/
};
enum ChangeNameResponse : int {
Denied = 0, // 5167: "You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name."
Accepted = 1, // 5976: "Your request for a name change was successful."
Timeout = -1, // 5977: "Your request for a name change has timed out. Please try again later."
ServerError = -2, // 5978: "The server had an error while processing your name request. Please try again later."
RateLimited = -3, // 5979: "You must wait longer before submitting another name request. Please try again in a few minutes."
Ineligible = -4, // 5980: "Your character is not eligible for a name change."
Pending = -5 // 5193: "You already have a name change pending. Please wait until it is fully processed before attempting another name change."
};
struct AltChangeName_Struct {
/*00*/ char new_name[64];
/*40*/ char old_name[64];
/*80*/ int response_code;
};
struct ChangePetName_Struct {
/*00*/ char new_pet_name[64];
/*40*/ char pet_owner_name[64];
/*80*/ int response_code;
};
enum ChangePetNameResponse : int {
Denied = 0, // 5167 You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name.
Accepted = 1, // 5976 Your request for a name change was successful.
Timeout = -3, // 5979 You must wait longer before submitting another name request. Please try again in a few minutes.
NotEligible = -4, // 5980 Your character is not eligible for a name change.
Pending = -5, // 5193 You already have a name change pending. Please wait until it is fully processed before attempting another name change.
Unhandled = -1
};
// New OpCode/Struct for SoD+
struct GroupMakeLeader_Struct
{
+9 -1
View File
@@ -614,7 +614,7 @@ void EQEmuLogSys::EnableConsoleLogging()
std::copy(std::begin(pre_silence_settings), std::end(pre_silence_settings), std::begin(log_settings));
}
EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings()
EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings(bool silent_load)
{
InjectTablesIfNotExist();
@@ -699,6 +699,10 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings()
return this;
}
if (silent_load) {
SilenceConsoleLogging();
}
LogInfo("Loaded [{}] log categories", categories.size());
auto webhooks = DiscordWebhooksRepository::GetWhere(*m_database, fmt::format("id < {}", MAX_DISCORD_WEBHOOK_ID));
@@ -716,6 +720,10 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings()
log_settings[Logs::Info].log_to_file = static_cast<uint8>(Logs::General);
log_settings[Logs::Info].log_to_console = static_cast<uint8>(Logs::General);
if (silent_load) {
SilenceConsoleLogging();
}
return this;
}
+1 -1
View File
@@ -279,7 +279,7 @@ public:
*/
void CloseFileLogs();
EQEmuLogSys *LoadLogSettingsDefaults();
EQEmuLogSys *LoadLogDatabaseSettings();
EQEmuLogSys *LoadLogDatabaseSettings(bool silent_load = false);
/**
* @param directory_name
+8
View File
@@ -303,6 +303,14 @@ bool IpUtil::IsPortInUse(const std::string& ip, int port) {
return true; // Assume in use on failure
}
#ifdef _WIN32
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&opt, sizeof(opt)); // Windows-specific
#else
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // Linux/macOS
#endif
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
+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;
}
}
}
@@ -24,6 +24,7 @@ public:
int16_t race;
int8_t gender;
int8_t texture;
int8_t helmtexture;
float mountspeed;
std::string notes;
};
@@ -41,6 +42,7 @@ public:
"race",
"gender",
"texture",
"helmtexture",
"mountspeed",
"notes",
};
@@ -54,6 +56,7 @@ public:
"race",
"gender",
"texture",
"helmtexture",
"mountspeed",
"notes",
};
@@ -96,13 +99,14 @@ public:
{
Horses e{};
e.id = 0;
e.filename = "";
e.race = 216;
e.gender = 0;
e.texture = 0;
e.mountspeed = 0.75;
e.notes = "Notes";
e.id = 0;
e.filename = "";
e.race = 216;
e.gender = 0;
e.texture = 0;
e.helmtexture = -1;
e.mountspeed = 0.75;
e.notes = "Notes";
return e;
}
@@ -139,13 +143,14 @@ public:
if (results.RowCount() == 1) {
Horses e{};
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.filename = row[1] ? row[1] : "";
e.race = row[2] ? static_cast<int16_t>(atoi(row[2])) : 216;
e.gender = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
e.texture = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
e.mountspeed = row[5] ? strtof(row[5], nullptr) : 0.75;
e.notes = row[6] ? row[6] : "Notes";
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.filename = row[1] ? row[1] : "";
e.race = row[2] ? static_cast<int16_t>(atoi(row[2])) : 216;
e.gender = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
e.texture = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
e.helmtexture = row[5] ? static_cast<int8_t>(atoi(row[5])) : -1;
e.mountspeed = row[6] ? strtof(row[6], nullptr) : 0.75;
e.notes = row[7] ? row[7] : "Notes";
return e;
}
@@ -183,8 +188,9 @@ public:
v.push_back(columns[2] + " = " + std::to_string(e.race));
v.push_back(columns[3] + " = " + std::to_string(e.gender));
v.push_back(columns[4] + " = " + std::to_string(e.texture));
v.push_back(columns[5] + " = " + std::to_string(e.mountspeed));
v.push_back(columns[6] + " = '" + Strings::Escape(e.notes) + "'");
v.push_back(columns[5] + " = " + std::to_string(e.helmtexture));
v.push_back(columns[6] + " = " + std::to_string(e.mountspeed));
v.push_back(columns[7] + " = '" + Strings::Escape(e.notes) + "'");
auto results = db.QueryDatabase(
fmt::format(
@@ -211,6 +217,7 @@ public:
v.push_back(std::to_string(e.race));
v.push_back(std::to_string(e.gender));
v.push_back(std::to_string(e.texture));
v.push_back(std::to_string(e.helmtexture));
v.push_back(std::to_string(e.mountspeed));
v.push_back("'" + Strings::Escape(e.notes) + "'");
@@ -247,6 +254,7 @@ public:
v.push_back(std::to_string(e.race));
v.push_back(std::to_string(e.gender));
v.push_back(std::to_string(e.texture));
v.push_back(std::to_string(e.helmtexture));
v.push_back(std::to_string(e.mountspeed));
v.push_back("'" + Strings::Escape(e.notes) + "'");
@@ -282,13 +290,14 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
Horses e{};
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.filename = row[1] ? row[1] : "";
e.race = row[2] ? static_cast<int16_t>(atoi(row[2])) : 216;
e.gender = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
e.texture = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
e.mountspeed = row[5] ? strtof(row[5], nullptr) : 0.75;
e.notes = row[6] ? row[6] : "Notes";
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.filename = row[1] ? row[1] : "";
e.race = row[2] ? static_cast<int16_t>(atoi(row[2])) : 216;
e.gender = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
e.texture = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
e.helmtexture = row[5] ? static_cast<int8_t>(atoi(row[5])) : -1;
e.mountspeed = row[6] ? strtof(row[6], nullptr) : 0.75;
e.notes = row[7] ? row[7] : "Notes";
all_entries.push_back(e);
}
@@ -313,13 +322,14 @@ public:
for (auto row = results.begin(); row != results.end(); ++row) {
Horses e{};
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.filename = row[1] ? row[1] : "";
e.race = row[2] ? static_cast<int16_t>(atoi(row[2])) : 216;
e.gender = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
e.texture = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
e.mountspeed = row[5] ? strtof(row[5], nullptr) : 0.75;
e.notes = row[6] ? row[6] : "Notes";
e.id = row[0] ? static_cast<int32_t>(atoi(row[0])) : 0;
e.filename = row[1] ? row[1] : "";
e.race = row[2] ? static_cast<int16_t>(atoi(row[2])) : 216;
e.gender = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
e.texture = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
e.helmtexture = row[5] ? static_cast<int8_t>(atoi(row[5])) : -1;
e.mountspeed = row[6] ? strtof(row[6], nullptr) : 0.75;
e.notes = row[7] ? row[7] : "Notes";
all_entries.push_back(e);
}
@@ -399,6 +409,7 @@ public:
v.push_back(std::to_string(e.race));
v.push_back(std::to_string(e.gender));
v.push_back(std::to_string(e.texture));
v.push_back(std::to_string(e.helmtexture));
v.push_back(std::to_string(e.mountspeed));
v.push_back("'" + Strings::Escape(e.notes) + "'");
@@ -428,6 +439,7 @@ public:
v.push_back(std::to_string(e.race));
v.push_back(std::to_string(e.gender));
v.push_back(std::to_string(e.texture));
v.push_back(std::to_string(e.helmtexture));
v.push_back(std::to_string(e.mountspeed));
v.push_back("'" + Strings::Escape(e.notes) + "'");
@@ -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) + "'");
@@ -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) + ")");
@@ -23,6 +23,7 @@ public:
uint32_t zone_id;
uint32_t instance_id;
int8_t is_corpse;
int8_t is_zone;
int32_t decay_in_seconds;
uint32_t npc_id;
uint32_t spawn2_id;
@@ -61,6 +62,7 @@ public:
"zone_id",
"instance_id",
"is_corpse",
"is_zone",
"decay_in_seconds",
"npc_id",
"spawn2_id",
@@ -95,6 +97,7 @@ public:
"zone_id",
"instance_id",
"is_corpse",
"is_zone",
"decay_in_seconds",
"npc_id",
"spawn2_id",
@@ -163,6 +166,7 @@ public:
e.zone_id = 0;
e.instance_id = 0;
e.is_corpse = 0;
e.is_zone = 0;
e.decay_in_seconds = 0;
e.npc_id = 0;
e.spawn2_id = 0;
@@ -227,30 +231,31 @@ public:
e.zone_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.instance_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.is_corpse = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
e.decay_in_seconds = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
e.npc_id = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.spawn2_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.spawngroup_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.x = row[8] ? strtof(row[8], nullptr) : 0;
e.y = row[9] ? strtof(row[9], nullptr) : 0;
e.z = row[10] ? strtof(row[10], nullptr) : 0;
e.heading = row[11] ? strtof(row[11], nullptr) : 0;
e.respawn_time = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
e.variance = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.grid = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.current_waypoint = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
e.path_when_zone_idle = row[16] ? static_cast<int16_t>(atoi(row[16])) : 0;
e.condition_id = row[17] ? static_cast<uint16_t>(strtoul(row[17], nullptr, 10)) : 0;
e.condition_min_value = row[18] ? static_cast<int16_t>(atoi(row[18])) : 0;
e.enabled = row[19] ? static_cast<int16_t>(atoi(row[19])) : 1;
e.anim = row[20] ? static_cast<uint16_t>(strtoul(row[20], nullptr, 10)) : 0;
e.loot_data = row[21] ? row[21] : "";
e.entity_variables = row[22] ? row[22] : "";
e.buffs = row[23] ? row[23] : "";
e.hp = row[24] ? strtoll(row[24], nullptr, 10) : 0;
e.mana = row[25] ? strtoll(row[25], nullptr, 10) : 0;
e.endurance = row[26] ? strtoll(row[26], nullptr, 10) : 0;
e.created_at = strtoll(row[27] ? row[27] : "-1", nullptr, 10);
e.is_zone = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
e.decay_in_seconds = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
e.npc_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.spawn2_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.spawngroup_id = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.x = row[9] ? strtof(row[9], nullptr) : 0;
e.y = row[10] ? strtof(row[10], nullptr) : 0;
e.z = row[11] ? strtof(row[11], nullptr) : 0;
e.heading = row[12] ? strtof(row[12], nullptr) : 0;
e.respawn_time = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.variance = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.grid = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
e.current_waypoint = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
e.path_when_zone_idle = row[17] ? static_cast<int16_t>(atoi(row[17])) : 0;
e.condition_id = row[18] ? static_cast<uint16_t>(strtoul(row[18], nullptr, 10)) : 0;
e.condition_min_value = row[19] ? static_cast<int16_t>(atoi(row[19])) : 0;
e.enabled = row[20] ? static_cast<int16_t>(atoi(row[20])) : 1;
e.anim = row[21] ? static_cast<uint16_t>(strtoul(row[21], nullptr, 10)) : 0;
e.loot_data = row[22] ? row[22] : "";
e.entity_variables = row[23] ? row[23] : "";
e.buffs = row[24] ? row[24] : "";
e.hp = row[25] ? strtoll(row[25], nullptr, 10) : 0;
e.mana = row[26] ? strtoll(row[26], nullptr, 10) : 0;
e.endurance = row[27] ? strtoll(row[27], nullptr, 10) : 0;
e.created_at = strtoll(row[28] ? row[28] : "-1", nullptr, 10);
return e;
}
@@ -287,30 +292,31 @@ public:
v.push_back(columns[1] + " = " + std::to_string(e.zone_id));
v.push_back(columns[2] + " = " + std::to_string(e.instance_id));
v.push_back(columns[3] + " = " + std::to_string(e.is_corpse));
v.push_back(columns[4] + " = " + std::to_string(e.decay_in_seconds));
v.push_back(columns[5] + " = " + std::to_string(e.npc_id));
v.push_back(columns[6] + " = " + std::to_string(e.spawn2_id));
v.push_back(columns[7] + " = " + std::to_string(e.spawngroup_id));
v.push_back(columns[8] + " = " + std::to_string(e.x));
v.push_back(columns[9] + " = " + std::to_string(e.y));
v.push_back(columns[10] + " = " + std::to_string(e.z));
v.push_back(columns[11] + " = " + std::to_string(e.heading));
v.push_back(columns[12] + " = " + std::to_string(e.respawn_time));
v.push_back(columns[13] + " = " + std::to_string(e.variance));
v.push_back(columns[14] + " = " + std::to_string(e.grid));
v.push_back(columns[15] + " = " + std::to_string(e.current_waypoint));
v.push_back(columns[16] + " = " + std::to_string(e.path_when_zone_idle));
v.push_back(columns[17] + " = " + std::to_string(e.condition_id));
v.push_back(columns[18] + " = " + std::to_string(e.condition_min_value));
v.push_back(columns[19] + " = " + std::to_string(e.enabled));
v.push_back(columns[20] + " = " + std::to_string(e.anim));
v.push_back(columns[21] + " = '" + Strings::Escape(e.loot_data) + "'");
v.push_back(columns[22] + " = '" + Strings::Escape(e.entity_variables) + "'");
v.push_back(columns[23] + " = '" + Strings::Escape(e.buffs) + "'");
v.push_back(columns[24] + " = " + std::to_string(e.hp));
v.push_back(columns[25] + " = " + std::to_string(e.mana));
v.push_back(columns[26] + " = " + std::to_string(e.endurance));
v.push_back(columns[27] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
v.push_back(columns[4] + " = " + std::to_string(e.is_zone));
v.push_back(columns[5] + " = " + std::to_string(e.decay_in_seconds));
v.push_back(columns[6] + " = " + std::to_string(e.npc_id));
v.push_back(columns[7] + " = " + std::to_string(e.spawn2_id));
v.push_back(columns[8] + " = " + std::to_string(e.spawngroup_id));
v.push_back(columns[9] + " = " + std::to_string(e.x));
v.push_back(columns[10] + " = " + std::to_string(e.y));
v.push_back(columns[11] + " = " + std::to_string(e.z));
v.push_back(columns[12] + " = " + std::to_string(e.heading));
v.push_back(columns[13] + " = " + std::to_string(e.respawn_time));
v.push_back(columns[14] + " = " + std::to_string(e.variance));
v.push_back(columns[15] + " = " + std::to_string(e.grid));
v.push_back(columns[16] + " = " + std::to_string(e.current_waypoint));
v.push_back(columns[17] + " = " + std::to_string(e.path_when_zone_idle));
v.push_back(columns[18] + " = " + std::to_string(e.condition_id));
v.push_back(columns[19] + " = " + std::to_string(e.condition_min_value));
v.push_back(columns[20] + " = " + std::to_string(e.enabled));
v.push_back(columns[21] + " = " + std::to_string(e.anim));
v.push_back(columns[22] + " = '" + Strings::Escape(e.loot_data) + "'");
v.push_back(columns[23] + " = '" + Strings::Escape(e.entity_variables) + "'");
v.push_back(columns[24] + " = '" + Strings::Escape(e.buffs) + "'");
v.push_back(columns[25] + " = " + std::to_string(e.hp));
v.push_back(columns[26] + " = " + std::to_string(e.mana));
v.push_back(columns[27] + " = " + std::to_string(e.endurance));
v.push_back(columns[28] + " = FROM_UNIXTIME(" + (e.created_at > 0 ? std::to_string(e.created_at) : "null") + ")");
auto results = db.QueryDatabase(
fmt::format(
@@ -336,6 +342,7 @@ public:
v.push_back(std::to_string(e.zone_id));
v.push_back(std::to_string(e.instance_id));
v.push_back(std::to_string(e.is_corpse));
v.push_back(std::to_string(e.is_zone));
v.push_back(std::to_string(e.decay_in_seconds));
v.push_back(std::to_string(e.npc_id));
v.push_back(std::to_string(e.spawn2_id));
@@ -393,6 +400,7 @@ public:
v.push_back(std::to_string(e.zone_id));
v.push_back(std::to_string(e.instance_id));
v.push_back(std::to_string(e.is_corpse));
v.push_back(std::to_string(e.is_zone));
v.push_back(std::to_string(e.decay_in_seconds));
v.push_back(std::to_string(e.npc_id));
v.push_back(std::to_string(e.spawn2_id));
@@ -454,30 +462,31 @@ public:
e.zone_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.instance_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.is_corpse = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
e.decay_in_seconds = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
e.npc_id = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.spawn2_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.spawngroup_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.x = row[8] ? strtof(row[8], nullptr) : 0;
e.y = row[9] ? strtof(row[9], nullptr) : 0;
e.z = row[10] ? strtof(row[10], nullptr) : 0;
e.heading = row[11] ? strtof(row[11], nullptr) : 0;
e.respawn_time = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
e.variance = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.grid = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.current_waypoint = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
e.path_when_zone_idle = row[16] ? static_cast<int16_t>(atoi(row[16])) : 0;
e.condition_id = row[17] ? static_cast<uint16_t>(strtoul(row[17], nullptr, 10)) : 0;
e.condition_min_value = row[18] ? static_cast<int16_t>(atoi(row[18])) : 0;
e.enabled = row[19] ? static_cast<int16_t>(atoi(row[19])) : 1;
e.anim = row[20] ? static_cast<uint16_t>(strtoul(row[20], nullptr, 10)) : 0;
e.loot_data = row[21] ? row[21] : "";
e.entity_variables = row[22] ? row[22] : "";
e.buffs = row[23] ? row[23] : "";
e.hp = row[24] ? strtoll(row[24], nullptr, 10) : 0;
e.mana = row[25] ? strtoll(row[25], nullptr, 10) : 0;
e.endurance = row[26] ? strtoll(row[26], nullptr, 10) : 0;
e.created_at = strtoll(row[27] ? row[27] : "-1", nullptr, 10);
e.is_zone = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
e.decay_in_seconds = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
e.npc_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.spawn2_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.spawngroup_id = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.x = row[9] ? strtof(row[9], nullptr) : 0;
e.y = row[10] ? strtof(row[10], nullptr) : 0;
e.z = row[11] ? strtof(row[11], nullptr) : 0;
e.heading = row[12] ? strtof(row[12], nullptr) : 0;
e.respawn_time = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.variance = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.grid = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
e.current_waypoint = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
e.path_when_zone_idle = row[17] ? static_cast<int16_t>(atoi(row[17])) : 0;
e.condition_id = row[18] ? static_cast<uint16_t>(strtoul(row[18], nullptr, 10)) : 0;
e.condition_min_value = row[19] ? static_cast<int16_t>(atoi(row[19])) : 0;
e.enabled = row[20] ? static_cast<int16_t>(atoi(row[20])) : 1;
e.anim = row[21] ? static_cast<uint16_t>(strtoul(row[21], nullptr, 10)) : 0;
e.loot_data = row[22] ? row[22] : "";
e.entity_variables = row[23] ? row[23] : "";
e.buffs = row[24] ? row[24] : "";
e.hp = row[25] ? strtoll(row[25], nullptr, 10) : 0;
e.mana = row[26] ? strtoll(row[26], nullptr, 10) : 0;
e.endurance = row[27] ? strtoll(row[27], nullptr, 10) : 0;
e.created_at = strtoll(row[28] ? row[28] : "-1", nullptr, 10);
all_entries.push_back(e);
}
@@ -506,30 +515,31 @@ public:
e.zone_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.instance_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.is_corpse = row[3] ? static_cast<int8_t>(atoi(row[3])) : 0;
e.decay_in_seconds = row[4] ? static_cast<int32_t>(atoi(row[4])) : 0;
e.npc_id = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.spawn2_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.spawngroup_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.x = row[8] ? strtof(row[8], nullptr) : 0;
e.y = row[9] ? strtof(row[9], nullptr) : 0;
e.z = row[10] ? strtof(row[10], nullptr) : 0;
e.heading = row[11] ? strtof(row[11], nullptr) : 0;
e.respawn_time = row[12] ? static_cast<uint32_t>(strtoul(row[12], nullptr, 10)) : 0;
e.variance = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.grid = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.current_waypoint = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
e.path_when_zone_idle = row[16] ? static_cast<int16_t>(atoi(row[16])) : 0;
e.condition_id = row[17] ? static_cast<uint16_t>(strtoul(row[17], nullptr, 10)) : 0;
e.condition_min_value = row[18] ? static_cast<int16_t>(atoi(row[18])) : 0;
e.enabled = row[19] ? static_cast<int16_t>(atoi(row[19])) : 1;
e.anim = row[20] ? static_cast<uint16_t>(strtoul(row[20], nullptr, 10)) : 0;
e.loot_data = row[21] ? row[21] : "";
e.entity_variables = row[22] ? row[22] : "";
e.buffs = row[23] ? row[23] : "";
e.hp = row[24] ? strtoll(row[24], nullptr, 10) : 0;
e.mana = row[25] ? strtoll(row[25], nullptr, 10) : 0;
e.endurance = row[26] ? strtoll(row[26], nullptr, 10) : 0;
e.created_at = strtoll(row[27] ? row[27] : "-1", nullptr, 10);
e.is_zone = row[4] ? static_cast<int8_t>(atoi(row[4])) : 0;
e.decay_in_seconds = row[5] ? static_cast<int32_t>(atoi(row[5])) : 0;
e.npc_id = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.spawn2_id = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.spawngroup_id = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.x = row[9] ? strtof(row[9], nullptr) : 0;
e.y = row[10] ? strtof(row[10], nullptr) : 0;
e.z = row[11] ? strtof(row[11], nullptr) : 0;
e.heading = row[12] ? strtof(row[12], nullptr) : 0;
e.respawn_time = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.variance = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.grid = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
e.current_waypoint = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
e.path_when_zone_idle = row[17] ? static_cast<int16_t>(atoi(row[17])) : 0;
e.condition_id = row[18] ? static_cast<uint16_t>(strtoul(row[18], nullptr, 10)) : 0;
e.condition_min_value = row[19] ? static_cast<int16_t>(atoi(row[19])) : 0;
e.enabled = row[20] ? static_cast<int16_t>(atoi(row[20])) : 1;
e.anim = row[21] ? static_cast<uint16_t>(strtoul(row[21], nullptr, 10)) : 0;
e.loot_data = row[22] ? row[22] : "";
e.entity_variables = row[23] ? row[23] : "";
e.buffs = row[24] ? row[24] : "";
e.hp = row[25] ? strtoll(row[25], nullptr, 10) : 0;
e.mana = row[26] ? strtoll(row[26], nullptr, 10) : 0;
e.endurance = row[27] ? strtoll(row[27], nullptr, 10) : 0;
e.created_at = strtoll(row[28] ? row[28] : "-1", nullptr, 10);
all_entries.push_back(e);
}
@@ -608,6 +618,7 @@ public:
v.push_back(std::to_string(e.zone_id));
v.push_back(std::to_string(e.instance_id));
v.push_back(std::to_string(e.is_corpse));
v.push_back(std::to_string(e.is_zone));
v.push_back(std::to_string(e.decay_in_seconds));
v.push_back(std::to_string(e.npc_id));
v.push_back(std::to_string(e.spawn2_id));
@@ -658,6 +669,7 @@ public:
v.push_back(std::to_string(e.zone_id));
v.push_back(std::to_string(e.instance_id));
v.push_back(std::to_string(e.is_corpse));
v.push_back(std::to_string(e.is_zone));
v.push_back(std::to_string(e.decay_in_seconds));
v.push_back(std::to_string(e.npc_id));
v.push_back(std::to_string(e.spawn2_id));
+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
@@ -5,9 +5,77 @@
#include "../strings.h"
#include "base/base_zone_state_spawns_repository.h"
class ZoneStateSpawnsRepository: public BaseZoneStateSpawnsRepository {
class ZoneStateSpawnsRepository : public BaseZoneStateSpawnsRepository {
public:
// Custom extended repository methods here
static void PurgeInvalidZoneStates(Database &database)
{
std::string query = R"(
SELECT zone_id, instance_id
FROM zone_state_spawns
GROUP BY zone_id, instance_id
HAVING COUNT(*) = SUM(
CASE
WHEN hp = 0
AND mana = 0
AND endurance = 0
AND (loot_data IS NULL OR loot_data = '')
AND (entity_variables IS NULL OR entity_variables = '')
AND (buffs IS NULL OR buffs = '')
THEN 1 ELSE 0
END
);
)";
auto results = database.QueryDatabase(query);
if (!results.Success()) {
return;
}
for (auto row: results) {
uint32 zone_id = std::stoul(row[0]);
uint32 instance_id = std::stoul(row[1]);
int rows = ZoneStateSpawnsRepository::DeleteWhere(
database,
fmt::format(
"`zone_id` = {} AND `instance_id` = {}",
zone_id,
instance_id
)
);
LogInfo(
"Purged invalid zone state data for zone [{}] instance [{}] rows [{}]",
zone_id,
instance_id,
Strings::Commify(rows)
);
}
}
static void PurgeOldZoneStates(Database &database)
{
int days = RuleI(Zone, StateSaveClearDays);
std::string query = fmt::format(
"DELETE FROM zone_state_spawns WHERE created_at < NOW() - INTERVAL {} DAY",
days
);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
LogError("Failed to purge old zone state data older than {} days.", days);
return;
}
if (results.RowsAffected() > 0) {
LogInfo(
"Purged old zone state data older than days [{}] rows [{}]",
days,
Strings::Commify(results.RowsAffected())
);
}
}
};
+26 -20
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")
@@ -231,6 +231,7 @@ RULE_INT(Character, MendAlwaysSucceedValue, 199, "Value at which mend will alway
RULE_BOOL(Character, SneakAlwaysSucceedOver100, false, "When sneak skill is over 100, always succeed sneak/hide. Default: false")
RULE_INT(Character, BandolierSwapDelay, 0, "Bandolier swap delay in milliseconds, default is 0")
RULE_BOOL(Character, EnableHackedFastCampForGM, false, "Enables hacked fast camp for GM clients, if the GM doesn't have a hacked client they'll camp like normal")
RULE_BOOL(Character, AlwaysAllowNameChange, false, "Enable this option to allow /changename to work without enabling a name change via scripts.")
RULE_CATEGORY_END()
RULE_CATEGORY(Mercs)
@@ -260,6 +261,7 @@ RULE_INT(Guild, TributeTime, 600000, "Time in ms for guild tributes. Default is
RULE_INT(Guild, TributeTimeRefreshInterval, 180000, "Time in ms to send all guild members a Tribute Time refresh. Default is 3 mins.")
RULE_INT(Guild, 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)
@@ -289,6 +291,7 @@ RULE_BOOL(Pets, ClientPetsUseOwnerNameInLastName, true, "Disable this to keep cl
RULE_BOOL(Pets, CanTakeNoDrop, false, "Setting whether anyone can give no-drop items to pets")
RULE_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)
@@ -374,7 +377,11 @@ RULE_BOOL(Zone, AllowCrossZoneSpellsOnBots, false, "Set to true to allow cross z
RULE_BOOL(Zone, AllowCrossZoneSpellsOnMercs, false, "Set to true to allow cross zone spells (cast/remove) to affect mercenaries")
RULE_BOOL(Zone, AllowCrossZoneSpellsOnPets, false, "Set to true to allow cross zone spells (cast/remove) to affect pets")
RULE_BOOL(Zone, ZoneShardQuestMenuOnly, false, "Set to true if you only want quests to show the zone shard menu")
RULE_BOOL(Zone, StateSaveEntityVariables, true, "Set to true if you want buffs to be saved on shutdown")
RULE_BOOL(Zone, 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)
@@ -384,9 +391,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)
@@ -810,6 +818,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%.")
@@ -821,8 +830,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.")
@@ -832,14 +839,14 @@ RULE_INT(Bots, MinGroupCureTargets, 3, "Minimum number of targets in valid range
RULE_INT(Bots, MinTargetsForAESpell, 3, "Minimum number of targets in valid range that are required for an AE spell to cast. Default 3.")
RULE_INT(Bots, MinTargetsForGroupSpell, 3, "Minimum number of targets in valid range that are required for an group spell to cast. Default 3.")
RULE_BOOL(Bots, AllowBuffingHealingFamiliars, false, "Determines if bots are allowed to buff and heal familiars. Default false.")
RULE_BOOL(Bots, RunSpellTypeChecksOnSpawn, false, "This will run a serious of checks on spell types and output errors to LogBotSpellTypeChecks")
RULE_BOOL(Bots, RunSpellTypeChecksOnBoot, false, "This will run a series of checks to find potential errors in your bot_spells_entries table on boot and output to LogBotSpellTypeChecks")
RULE_BOOL(Bots, UseParentSpellTypeForChecks, true, "This will check only the parent instead of AE/Group/Pet types (ex: AENukes/AERains/PBAENukes fall under Nukes or PetBuffs fall under buffs) when RunSpellTypeChecksOnSpawn fires")
RULE_BOOL(Bots, AllowForcedCastsBySpellID, true, "If enabled, players can use ^cast spellid # to cast a specific spell by ID that is in their spell list")
RULE_BOOL(Bots, AllowCastAAs, true, "If enabled, players can use ^cast aa to cast a clickable AA")
RULE_BOOL(Bots, 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")
@@ -850,17 +857,13 @@ 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_BOOL(Bots, UseFlatNormalMeleeRange, false, "False Default. If true, bots melee distance will be a flat distance set by Bots:NormalMeleeRangeDistance.")
RULE_REAL(Bots, NormalMeleeRangeDistance, 0.75, "If UseFlatNormalMeleeRange is enabled, multiplier of the max melee range at which a bot will stand in melee combat. 0.75 Recommended, max melee for all abilities to land.")
RULE_REAL(Bots, PercentMinMeleeDistance, 0.75, "Multiplier of the their melee range - Minimum distance from target a bot will stand while in melee combat before trying to adjust. 0.60 Recommended.")
RULE_REAL(Bots, MaxDistanceForMelee, 20, "Maximum distance bots will stand for melee. Default 20 to allow all special attacks to land.")
RULE_REAL(Bots, TauntNormalMeleeRangeDistance, 0.50, "Multiplier of the max melee range at which a taunting bot will stand in melee combat. 0.50 Recommended, closer than others .")
RULE_REAL(Bots, PercentTauntMinMeleeDistance, 0.40, "Multiplier of their melee range - Minimum distance from target a taunting bot will stand while in melee combat before trying to adjust. 0.25 Recommended.")
RULE_REAL(Bots, PercentMaxMeleeRangeDistance, 0.95, "Multiplier of the max melee range at which a bot will stand in melee combat. 0.95 Recommended, max melee while disabling special attacks/taunt.")
RULE_REAL(Bots, PercentMinMaxMeleeRangeDistance, 0.75, "Multiplier of the closest max melee range at which a bot will stand in melee combat before trying to adjust. 0.75 Recommended, max melee while disabling special attacks/taunt.")
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_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "True Default. If true, when bots are at max melee distance, special abilities including taunt will be disabled.")
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_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.")
@@ -901,6 +904,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)
@@ -1092,6 +1096,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)
@@ -1163,8 +1168,9 @@ RULE_CATEGORY_END()
RULE_CATEGORY(EvolvingItems)
RULE_REAL(EvolvingItems, PercentOfSoloExperience, 0.1, "Percentage of solo experience allocated to evolving items that require experience.")
RULE_REAL(EvolvingItems, PercentOfGroupExperience, 0.1, "Percentage of group experience allocated to evolving items that require experience.")
RULE_REAL(EvolvingItems, PercentOfRaidExperience, 0.1, "Percentage of solo experience allocated to evolving items that require experience.")
RULE_REAL(EvolvingItems, PercentOfRaidExperience, 0.1, "Percentage of raid experience allocated to evolving items that require experience.")
RULE_INT(EvolvingItems, DelayUponEquipping, 30000, "Delay in ms before an evolving item will earn rewards after equipping. Default is 30000ms or 30s.")
RULE_BOOL(EvolvingItems, DestroyAugmentsOnEvolve, false, "If this is enabled, any augments in an item will be destroyed when the item evolves. Otherwise, send augments to the player via the parcel system (requires that the Parcel System be enabled).")
RULE_CATEGORY_END()
#undef RULE_CATEGORY
+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",
+8
View File
@@ -196,6 +196,7 @@
#define ServerOP_DzSaveInvite 0x0466
#define ServerOP_DzRequestInvite 0x0467
#define ServerOP_DzMakeLeader 0x0468
#define ServerOP_DzGetBulkMemberStatuses 0x0469
#define ServerOP_LSInfo 0x1000
#define ServerOP_LSStatus 0x1001
@@ -1555,6 +1556,13 @@ struct ServerDzMemberStatuses_Struct {
ServerDzMemberStatusEntry_Struct entries[0];
};
struct ServerDzCerealData_Struct {
uint16_t zone_id;
uint16_t inst_id;
uint32_t cereal_size;
char cereal_data[1];
};
struct ServerDzMovePC_Struct {
uint32 dz_id;
uint16 sender_zone_id;
+49 -49
View File
@@ -1456,41 +1456,42 @@ bool IsCompleteHealSpell(uint16 spell_id)
}
bool IsFastHealSpell(uint16 spell_id) {
spell_id = (
IsEffectInSpell(spell_id, SE_CurrentHP) ?
spell_id :
GetSpellTriggerSpellID(spell_id, SE_CurrentHP)
);
spell_id = (
IsEffectInSpell(spell_id, SE_CurrentHP) ?
spell_id :
GetSpellTriggerSpellID(spell_id, SE_CurrentHP)
);
if (!spell_id) {
spell_id = (
IsEffectInSpell(spell_id, SE_CurrentHPOnce) ?
spell_id :
GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce)
);
}
if (!spell_id) {
spell_id = (
IsEffectInSpell(spell_id, SE_CurrentHPOnce) ?
spell_id :
GetSpellTriggerSpellID(spell_id, SE_CurrentHPOnce)
);
}
if (spell_id && IsValidSpell(spell_id)) {
if (
spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME &&
spells[spell_id].good_effect &&
!IsGroupSpell(spell_id)
) {
for (int i = 0; i < EFFECT_COUNT; i++) {
if (
spells[spell_id].base_value[i] > 0 &&
(
spells[spell_id].effect_id[i] == SE_CurrentHP ||
spells[spell_id].effect_id[i] == SE_CurrentHPOnce
)
) {
return true;
}
}
}
}
if (IsValidSpell(spell_id)) {
if (
spell_id != SPELL_MINOR_HEALING &&
(spells[spell_id].cast_time > MAX_VERY_FAST_HEAL_CASTING_TIME && spells[spell_id].cast_time <= MAX_FAST_HEAL_CASTING_TIME) &&
spells[spell_id].good_effect &&
!IsGroupSpell(spell_id)
) {
for (int i = 0; i < EFFECT_COUNT; i++) {
if (
spells[spell_id].base_value[i] > 0 &&
(
spells[spell_id].effect_id[i] == SE_CurrentHP ||
spells[spell_id].effect_id[i] == SE_CurrentHPOnce
)
) {
return true;
}
}
}
}
return false;
return false;
}
bool IsVeryFastHealSpell(uint16 spell_id)
@@ -1509,8 +1510,9 @@ bool IsVeryFastHealSpell(uint16 spell_id)
);
}
if (spell_id) {
if (IsValidSpell(spell_id)) {
if (
spell_id != SPELL_MINOR_HEALING &&
spells[spell_id].cast_time <= MAX_VERY_FAST_HEAL_CASTING_TIME &&
spells[spell_id].good_effect &&
!IsGroupSpell(spell_id)
@@ -1548,8 +1550,13 @@ bool IsRegularSingleTargetHealSpell(uint16 spell_id)
);
}
if (spell_id) {
if (IsValidSpell(spell_id)) {
if (spell_id == SPELL_MINOR_HEALING) {
return true;
}
if (
spells[spell_id].cast_time > MAX_FAST_HEAL_CASTING_TIME &&
spells[spell_id].target_type == ST_Target &&
!IsCompleteHealSpell(spell_id) &&
!IsHealOverTimeSpell(spell_id) &&
@@ -1589,9 +1596,14 @@ bool IsRegularPetHealSpell(uint16 spell_id)
);
}
if (spell_id && IsValidSpell(spell_id)) {
if (IsValidSpell(spell_id)) {
if (spell_id == SPELL_MINOR_HEALING) {
return true;
}
if (
(spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_Undead) &&
spells[spell_id].cast_time > MAX_FAST_HEAL_CASTING_TIME &&
(spells[spell_id].target_type == ST_Pet || spells[spell_id].target_type == ST_SummonedPet) &&
!IsCompleteHealSpell(spell_id) &&
!IsHealOverTimeSpell(spell_id) &&
!IsGroupSpell(spell_id)
@@ -1630,7 +1642,7 @@ bool IsRegularGroupHealSpell(uint16 spell_id)
);
}
if (spell_id) {
if (IsValidSpell(spell_id)) {
if (
IsGroupSpell(spell_id) &&
!IsCompleteHealSpell(spell_id) &&
@@ -2796,18 +2808,6 @@ bool IsLichSpell(uint16 spell_id)
);
}
bool IsValidSpellAndLoS(uint32 spell_id, bool has_los) {
if (!IsValidSpell(spell_id)) {
return false;
}
if (!has_los && IsTargetRequiredForSpell(spell_id)) {
return false;
}
return true;
}
bool IsInstantHealSpell(uint32 spell_id) {
if (!IsValidSpell(spell_id)) {
return false;
+11 -8
View File
@@ -215,6 +215,7 @@
#define SPELL_AMPLIFICATION 2603
#define SPELL_DIVINE_REZ 2738
#define SPELL_NATURES_RECOVERY 2520
#define SPELL_MINOR_HEALING 200
#define SPELL_ADRENALINE_SWELL 14445
#define SPELL_ADRENALINE_SWELL_RK2 14446
#define SPELL_ADRENALINE_SWELL_RK3 14447
@@ -736,11 +737,12 @@ namespace BotSpellTypes
constexpr uint16 DiscUtility = 203;
constexpr uint16 START = BotSpellTypes::Nuke; // Do not remove or change this
constexpr uint16 END = BotSpellTypes::PetResistBuffs; // Do not remove this, increment as needed
constexpr uint16 COMMANDED_START = BotSpellTypes::Lull; // Do not remove or change this
constexpr uint16 COMMANDED_END = BotSpellTypes::AELull; // Do not remove this, increment as needed
constexpr uint16 DISCIPLINE_START = BotSpellTypes::Discipline; // Do not remove or change this
constexpr uint16 DISCIPLINE_END = BotSpellTypes::DiscUtility; // Do not remove this, increment as needed
constexpr uint16 END = BotSpellTypes::PetResistBuffs; // Do not remove this, increment as needed
constexpr uint16 COMMANDED_START = BotSpellTypes::Lull; // Do not remove or change this
constexpr uint16 COMMANDED_END = BotSpellTypes::AELull; // Do not remove this, increment as needed
constexpr uint16 DISCIPLINE_START = BotSpellTypes::Discipline; // Do not remove or change this
constexpr uint16 DISCIPLINE_END = BotSpellTypes::DiscUtility; // Do not remove this, increment as needed
constexpr uint16 PARENT_TYPE_END = BotSpellTypes::PreCombatBuffSong; // This is the last ID of the original bot spell types, the rest are considered sub types.
}
static std::map<uint16, std::string> spell_type_names = {
@@ -898,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);
@@ -915,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
@@ -1812,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);
+40 -252
View File
@@ -1,4 +1,5 @@
#include "spdat.h"
#include "../zone/bot.h"
bool IsBotSpellTypeDetrimental(uint16 spell_type) {
switch (spell_type) {
@@ -417,264 +418,23 @@ uint16 GetCorrectBotSpellType(uint16 spell_type, uint16 spell_id) {
return UINT16_MAX;
}
uint16 correct_type = UINT16_MAX;
SPDat_Spell_Struct spell = spells[spell_id];
std::string teleport_zone = spell.teleport_zone;
uint16 correct_type = spell_type;
if (IsCharmSpell(spell_id)) {
correct_type = BotSpellTypes::Charm;
}
else if (IsFearSpell(spell_id)) {
correct_type = BotSpellTypes::Fear;
}
else if (IsEffectInSpell(spell_id, SE_Revive)) {
correct_type = BotSpellTypes::Resurrect;
}
else if (IsHarmonySpell(spell_id)) {
correct_type = BotSpellTypes::Lull;
}
else if (
teleport_zone.compare("") &&
!IsEffectInSpell(spell_id, SE_GateToHomeCity) &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
) {
correct_type = BotSpellTypes::Teleport;
}
else if (
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_Succor)
) {
correct_type = BotSpellTypes::Succor;
}
else if (IsEffectInSpell(spell_id, SE_BindAffinity)) {
correct_type = BotSpellTypes::BindAffinity;
}
else if (IsEffectInSpell(spell_id, SE_Identify)) {
correct_type = BotSpellTypes::Identify;
}
else if (
spell_type == BotSpellTypes::Levitate &&
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_Levitate)
) {
correct_type = BotSpellTypes::Levitate;
}
else if (
spell_type == BotSpellTypes::Rune &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune))
) {
correct_type = BotSpellTypes::Rune;
}
else if (
spell_type == BotSpellTypes::WaterBreathing &&
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_WaterBreathing)
) {
correct_type = BotSpellTypes::WaterBreathing;
}
else if (
spell_type == BotSpellTypes::Size &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))
) {
correct_type = BotSpellTypes::Size;
}
else if (
spell_type == BotSpellTypes::Invisibility &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_SeeInvis) || IsInvisibleSpell(spell_id))
) {
correct_type = BotSpellTypes::Invisibility;
}
else if (
spell_type == BotSpellTypes::MovementSpeed &&
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_MovementSpeed)
) {
correct_type = BotSpellTypes::MovementSpeed;
}
else if (
!teleport_zone.compare("") &&
IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_Translocate) || IsEffectInSpell(spell_id, SE_GateToHomeCity))
) {
correct_type = BotSpellTypes::SendHome;
}
else if (IsEffectInSpell(spell_id, SE_SummonCorpse)) {
correct_type = BotSpellTypes::SummonCorpse;
}
if (!Bot::IsValidSpellTypeBySpellID(spell_type, spell_id)) {
correct_type = UINT16_MAX;
if (correct_type == UINT16_MAX) {
if (
IsSummonPetSpell(spell_id) ||
IsEffectInSpell(spell_id, SE_TemporaryPets)
) {
correct_type = BotSpellTypes::Pet;
}
else if (IsMesmerizeSpell(spell_id)) {
correct_type = BotSpellTypes::Mez;
}
else if (IsEscapeSpell(spell_id)) {
correct_type = BotSpellTypes::Escape;
}
else if (
IsDetrimentalSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_Root)
) {
if (IsAnyAESpell(spell_id)) {
correct_type = BotSpellTypes::AERoot;
}
else {
correct_type = BotSpellTypes::Root;
}
}
else if (
IsDetrimentalSpell(spell_id) &&
IsLifetapSpell(spell_id)
) {
correct_type = BotSpellTypes::Lifetap;
}
else if (
IsDetrimentalSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_MovementSpeed)
) {
correct_type = BotSpellTypes::Snare;
}
else if (
IsDetrimentalSpell(spell_id) &&
(IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id))
) {
correct_type = BotSpellTypes::DOT;
}
else if (IsDispelSpell(spell_id)) {
correct_type = BotSpellTypes::Dispel;
}
else if (
IsDetrimentalSpell(spell_id) &&
IsSlowSpell(spell_id)
) {
correct_type = BotSpellTypes::Slow;
}
else if (
IsDebuffSpell(spell_id) &&
!IsHateReduxSpell(spell_id) &&
!IsHateSpell(spell_id)
) {
correct_type = BotSpellTypes::Debuff;
}
else if (IsHateReduxSpell(spell_id)) {
correct_type = BotSpellTypes::HateRedux;
}
else if (
IsDetrimentalSpell(spell_id) &&
IsHateSpell(spell_id)
) {
correct_type = BotSpellTypes::HateLine;
}
else if (
IsBuffSpell(spell_id) &&
IsBeneficialSpell(spell_id) &&
IsBardSong(spell_id)
) {
if (
spell_type == BotSpellTypes::InCombatBuffSong ||
spell_type == BotSpellTypes::OutOfCombatBuffSong ||
spell_type == BotSpellTypes::PreCombatBuffSong
) {
correct_type = spell_type;
}
else {
correct_type = BotSpellTypes::OutOfCombatBuffSong;
}
}
else if (
!IsBardSong(spell_id) &&
(
(IsSelfConversionSpell(spell_id) && spell.buff_duration < 1) ||
(spell_type == BotSpellTypes::InCombatBuff && IsAnyBuffSpell(spell_id))
)
) {
correct_type = BotSpellTypes::InCombatBuff;
}
else if (
spell_type == BotSpellTypes::PreCombatBuff &&
IsAnyBuffSpell(spell_id) &&
!IsBardSong(spell_id)
) {
correct_type = BotSpellTypes::PreCombatBuff;
}
else if (
(IsCureSpell(spell_id) && spell_type == BotSpellTypes::Cure) ||
(IsCureSpell(spell_id) && !IsAnyHealSpell(spell_id))
) {
correct_type = BotSpellTypes::Cure;
}
else if (IsAnyNukeOrStunSpell(spell_id)) {
if (IsAnyAESpell(spell_id)) {
if (IsAERainSpell(spell_id)) {
correct_type = BotSpellTypes::AERains;
}
else if (IsPBAENukeSpell(spell_id)) {
correct_type = BotSpellTypes::PBAENuke;
}
else if (IsStunSpell(spell_id)) {
correct_type = BotSpellTypes::AEStun;
}
else {
correct_type = BotSpellTypes::AENukes;
}
}
else if (IsStunSpell(spell_id)) {
correct_type = BotSpellTypes::Stun;
}
else {
correct_type = BotSpellTypes::Nuke;
}
}
else if (IsAnyHealSpell(spell_id)) {
if (IsGroupSpell(spell_id)) {
if (IsGroupCompleteHealSpell(spell_id)) {
correct_type = BotSpellTypes::GroupCompleteHeals;
}
else if (IsGroupHealOverTimeSpell(spell_id)) {
correct_type = BotSpellTypes::GroupHoTHeals;
}
else if (IsRegularGroupHealSpell(spell_id)) {
correct_type = BotSpellTypes::GroupHeals;
}
auto start = std::min({ BotSpellTypes::START, BotSpellTypes::COMMANDED_START, BotSpellTypes::DISCIPLINE_START });
auto end = std::max({ BotSpellTypes::END, BotSpellTypes::COMMANDED_END, BotSpellTypes::DISCIPLINE_END });
return correct_type;
for (int i = end; i >= start; --i) {
if (!Bot::IsValidBotSpellType(i) || i == BotSpellTypes::InCombatBuff) {
continue;
}
if (IsVeryFastHealSpell(spell_id)) {
correct_type = BotSpellTypes::VeryFastHeals;
}
else if (IsFastHealSpell(spell_id)) {
correct_type = BotSpellTypes::FastHeals;
}
else if (IsCompleteHealSpell(spell_id)) {
correct_type = BotSpellTypes::CompleteHeal;
}
else if (IsHealOverTimeSpell(spell_id)) {
correct_type = BotSpellTypes::HoTHeals;
}
else if (IsRegularSingleTargetHealSpell(spell_id)) {
correct_type = BotSpellTypes::RegularHeal;
}
else if (IsRegularPetHealSpell(spell_id)) {
correct_type = BotSpellTypes::RegularHeal;
}
}
else if (IsAnyBuffSpell(spell_id)) {
correct_type = BotSpellTypes::Buff;
if (Bot::IsValidSpellTypeBySpellID(i, spell_id)) {
correct_type = i;
if (IsResistanceOnlySpell(spell_id)) {
correct_type = BotSpellTypes::ResistBuffs;
}
else if (IsDamageShieldOnlySpell(spell_id)) {
correct_type = BotSpellTypes::DamageShields;
break;
}
}
}
@@ -708,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;
}
+8
View File
@@ -936,3 +936,11 @@ std::string Strings::Slugify(const std::string& input, const std::string& separa
return slug;
}
bool Strings::IsValidJson(const std::string &json)
{
rapidjson::Document doc;
rapidjson::ParseResult result = doc.Parse(json.c_str());
return result;
}
+2
View File
@@ -45,6 +45,7 @@
#include <type_traits>
#include <fmt/format.h>
#include <cereal/external/rapidjson/document.h>
#ifndef _WIN32
// this doesn't appear to affect linux-based systems..need feedback for _WIN64
@@ -188,6 +189,7 @@ public:
}
static std::string Slugify(const std::string &input, const std::string &separator = "-");
static bool IsValidJson(const std::string& json);
};
const std::string StringFormat(const char *format, ...);
+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();
+2 -2
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "23.2.0-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "23.4.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,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9309
#define CURRENT_BINARY_DATABASE_VERSION 9321
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
#endif
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "23.2.0",
"version": "23.4.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()));
+3
View File
@@ -746,3 +746,6 @@ OP_TradeSkillRecipeInspect=0x4f7e
OP_InvokeChangePetNameImmediate=0x046d
OP_InvokeChangePetName=0x4506
OP_ChangePetName=0x5dab
OP_InvokeNameChangeImmediate=0x4fe2
OP_InvokeNameChangeLazy=0x2f2e
+3 -1
View File
@@ -56,8 +56,10 @@ echo "# Running shared_memory"
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" test_output.log; then
if grep -E -q "QueryErr|Error|FAILED" test_output.log; then
echo "Error found in test output! Failing build."
exit 1
fi
+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.31.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/net v0.36.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.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
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/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.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
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/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=
+69
View File
@@ -1850,3 +1850,72 @@ std::map<uint32, ClientListEntry *> ClientList::GetGuildClientsWithTributeOptIn(
}
return guild_members;
}
#include <unordered_set>
std::vector<uint32_t> ClientList::GetGuildZoneServers(uint32 guild_id)
{
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;
}
std::vector<uint32_t> ClientList::GetZoneServersWithGMs()
{
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->Admin() > 0) {
uint32_t id = cle->Server()->GetID();
if (seen_ids.insert(id).second) {
zone_server_ids.emplace_back(id);
}
}
iterator.Advance();
}
return zone_server_ids;
}
+2
View File
@@ -60,6 +60,8 @@ public:
void CLCheckStale();
void CLEKeepAlive(uint32 numupdates, uint32* wid);
void CLEAdd(uint32 login_server_id, const char* login_server_name, const char* login_name, const char* login_key, int16 world_admin = AccountStatus::Player, uint32 ip_address = 0, uint8 is_local=0);
std::vector<uint32_t> GetGuildZoneServers(uint32 guild_id);
std::vector<uint32_t> GetZoneServersWithGMs();
void UpdateClientGuild(uint32 char_id, uint32 guild_id);
bool IsAccountInGame(uint32 iLSID);
+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
}
+37
View File
@@ -7,6 +7,7 @@
#include "zoneserver.h"
#include "../common/rulesys.h"
#include "../common/repositories/dynamic_zone_lockouts_repository.h"
#include <cereal/types/utility.hpp>
extern ClientList client_list;
extern ZSList zoneserver_list;
@@ -169,6 +170,33 @@ void DynamicZoneManager::LoadTemplates()
}
}
void DynamicZoneManager::SendBulkMemberStatuses(uint32_t zone_id, uint16_t inst_id)
{
std::vector<std::pair<uint32_t, std::vector<DynamicZoneMember>>> dzs;
dzs.reserve(dynamic_zone_cache.size());
for (const auto& [dz_id, dz] : dynamic_zone_cache)
{
dzs.emplace_back(dz_id, dz->GetMembers());
}
std::ostringstream ss;
{
cereal::BinaryOutputArchive archive(ss);
archive(dzs);
}
std::string_view sv = ss.view();
size_t size = sizeof(ServerDzCerealData_Struct) + sv.size();
ServerPacket pack(ServerOP_DzGetBulkMemberStatuses, static_cast<uint32_t>(size));
auto buf = reinterpret_cast<ServerDzCerealData_Struct*>(pack.pBuffer);
buf->cereal_size = static_cast<uint32_t>(sv.size());
memcpy(buf->cereal_data, sv.data(), sv.size());
zoneserver_list.SendPacket(zone_id, inst_id, &pack);
}
void DynamicZoneManager::HandleZoneMessage(ServerPacket* pack)
{
switch (pack->opcode)
@@ -338,6 +366,15 @@ void DynamicZoneManager::HandleZoneMessage(ServerPacket* pack)
}
break;
}
case ServerOP_DzGetBulkMemberStatuses:
{
auto buf = reinterpret_cast<ServerDzCerealData_Struct*>(pack->pBuffer);
if (buf->zone_id != 0 && !dynamic_zone_cache.empty())
{
SendBulkMemberStatuses(buf->zone_id, buf->inst_id);
}
break;
}
case ServerOP_DzUpdateMemberStatus:
{
auto buf = reinterpret_cast<ServerDzMemberStatus_Struct*>(pack->pBuffer);
+2
View File
@@ -30,6 +30,8 @@ public:
std::unordered_map<uint32_t, std::unique_ptr<DynamicZone>> dynamic_zone_cache;
private:
void SendBulkMemberStatuses(uint32_t zone_id, uint16_t inst_id);
Timer m_process_throttle_timer{};
std::unordered_map<uint32_t, DynamicZoneTemplatesRepository::DynamicZoneTemplates> m_dz_templates;
};
+2
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();
+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.SendPacketToZonesWithGuild(s->guild_id, 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)
}
+6
View File
@@ -27,6 +27,7 @@
#include "../common/zone_store.h"
#include "../common/path_manager.h"
#include "../common/database/database_update.h"
#include "../common/repositories/zone_state_spawns_repository.h"
extern ZSList zoneserver_list;
extern WorldConfig Config;
@@ -412,6 +413,11 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv)
LogInfo("Cleaning up instance corpses");
database.CleanupInstanceCorpses();
if (RuleB(Zone, StateSavingOnShutdown)) {
ZoneStateSpawnsRepository::PurgeInvalidZoneStates(database);
ZoneStateSpawnsRepository::PurgeOldZoneStates(database);
}
return true;
}
+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;
+68 -14
View File
@@ -36,11 +36,13 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "shared_task_manager.h"
#include "dynamic_zone_manager.h"
#include "ucs.h"
#include "clientlist.h"
extern uint32 numzones;
extern EQ::Random emu_random;
extern WebInterfaceList web_interface;
extern SharedTaskManager shared_task_manager;
extern ClientList client_list;
volatile bool UCSServerAvailable_ = false;
void CatchSignal(int sig_num);
@@ -515,19 +517,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 +545,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 +896,33 @@ 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)
{
for (auto const &z: zone_server_list) {
for (auto const &server_id: client_list.GetZoneServersWithGMs()) {
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 +946,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
};
+2
View File
@@ -29,6 +29,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);
+17 -8
View File
@@ -571,7 +571,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 +735,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"
);
}
@@ -1480,6 +1488,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
case ServerOP_DzSwapMembers:
case ServerOP_DzRemoveAllMembers:
case ServerOP_DzGetMemberStatuses:
case ServerOP_DzGetBulkMemberStatuses:
case ServerOP_DzSetSecondsRemaining:
case ServerOP_DzSetCompass:
case ServerOP_DzSetSafeReturn:
@@ -1509,7 +1518,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;
}
@@ -1552,7 +1561,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;
}
@@ -1586,7 +1595,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;
@@ -1609,7 +1618,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);
}
@@ -1629,7 +1638,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);
}
@@ -1653,7 +1662,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;
+2
View File
@@ -53,6 +53,8 @@ public:
inline const char* GetZoneName() const { return zone_name; }
inline const char* GetZoneLongName() const { return long_name; }
inline std::string GetCurrentVersion() const { return CURRENT_VERSION; }
inline std::string GetCompileDate() const { return COMPILE_DATE; }
const char* GetCompileTime() const{ return compiled; }
void SetCompile(char* in_compile){ strcpy(compiled,in_compile); }
inline uint32 GetZoneID() const { return zone_server_zone_id; }
+2
View File
@@ -173,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})
+13 -1
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)) ||
@@ -3041,9 +3047,15 @@ void Mob::AddToHateList(Mob* other, int64 hate /*= 0*/, int64 damage /*= 0*/, bo
if (!other)
return;
if (other->IsDestroying())
return;
if (other == this)
return;
if (other->IsClient() && (other->CastToClient()->IsZoning() || other->CastToClient()->Connected() == false))
return;
if (other->IsTrap())
return;
+5 -1
View File
@@ -690,12 +690,16 @@ void Aura::ProcessSpawns()
continue;
}
if (!e.second->IsOfClientBot()) {
if (!e.second->IsClient()) {
continue;
}
auto c = e.second->CastToClient();
if (!c) {
continue;
}
bool spawned = spawned_for.find(c->GetID()) != spawned_for.end();
if (ShouldISpawnFor(c)) {
if (!spawned) {
+13 -13
View File
@@ -318,13 +318,13 @@ void Mob::AddItemBonuses(const EQ::ItemInstance* inst, StatBonuses* b, bool is_a
b->HeroicWIS += CalcItemBonus(item->HeroicWis);
b->HeroicCHA += CalcItemBonus(item->HeroicCha);
b->STRCapMod += b->HeroicSTR;
b->STACapMod += b->HeroicSTA;
b->DEXCapMod += b->HeroicDEX;
b->AGICapMod += b->HeroicAGI;
b->INTCapMod += b->HeroicINT;
b->WISCapMod += b->HeroicWIS;
b->CHACapMod += b->HeroicCHA;
b->STRCapMod += item->HeroicStr;
b->STACapMod += item->HeroicSta;
b->DEXCapMod += item->HeroicDex;
b->AGICapMod += item->HeroicAgi;
b->INTCapMod += item->HeroicInt;
b->WISCapMod += item->HeroicWis;
b->CHACapMod += item->HeroicCha;
b->MR += CalcItemBonus(item->MR + item->HeroicMR);
b->FR += CalcItemBonus(item->FR + item->HeroicFR);
@@ -340,12 +340,12 @@ void Mob::AddItemBonuses(const EQ::ItemInstance* inst, StatBonuses* b, bool is_a
b->HeroicDR += CalcItemBonus(item->HeroicDR);
b->HeroicCorrup += CalcItemBonus(item->HeroicSVCorrup);
b->MRCapMod += b->HeroicMR;
b->FRCapMod += b->HeroicFR;
b->CRCapMod += b->HeroicCR;
b->PRCapMod += b->HeroicPR;
b->DRCapMod += b->HeroicDR;
b->CorrupCapMod += b->HeroicCorrup;
b->MRCapMod += item->HeroicMR;
b->FRCapMod += item->HeroicFR;
b->CRCapMod += item->HeroicCR;
b->PRCapMod += item->HeroicPR;
b->DRCapMod += item->HeroicDR;
b->CorrupCapMod += item->HeroicSVCorrup;
b->HPRegen += CalcItemBonus(item->Regen);
b->ManaRegen += CalcItemBonus(item->ManaRegen);
+399 -229
View File
@@ -2256,7 +2256,7 @@ void Bot::AI_Process()
SetAttackingFlag(false);
}
float tar_distance = DistanceSquared(m_Position, tar->GetPosition());
float tar_distance = DistanceSquaredNoZ(m_Position, tar->GetPosition());
// TARGET VALIDATION
if (!IsValidTarget(bot_owner, leash_owner, lo_distance, leash_distance, tar, tar_distance)) {
@@ -2287,7 +2287,7 @@ void Bot::AI_Process()
// COMBAT RANGE CALCS
bool front_mob = InFrontMob(tar, GetX(), GetY());
bool behind_mob = BehindMob(tar, GetX(), GetY());
uint8 stop_melee_level = GetStopMeleeLevel();
bool stop_melee_level = GetLevel() >= GetStopMeleeLevel();
tar_distance = sqrt(tar_distance); // sqrt this for future calculations
// Item variables
const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary);
@@ -2296,7 +2296,6 @@ void Bot::AI_Process()
CombatRangeInput input = {
.target = tar,
.target_distance = tar_distance,
.behind_mob = behind_mob,
.stop_melee_level = stop_melee_level,
.p_item = p_item,
.s_item = s_item
@@ -2315,7 +2314,7 @@ void Bot::AI_Process()
if (PULLING_BOT || RETURNING_BOT) {
if (!TargetValidation(tar)) { return; }
if (!DoLosChecks(tar)) {
if (RuleB(Bots, BotsRequireLoS) && !HasLoS()) {
return;
}
@@ -2368,6 +2367,16 @@ void Bot::AI_Process()
return;
}
if (
HasTargetReflection() &&
!IsTaunting() &&
!tar->IsFleeing() &&
!tar->IsFeared() &&
TryEvade(tar)
) {
return;
}
// ENGAGED AT COMBAT RANGE
// We can fight
@@ -2433,8 +2442,12 @@ void Bot::AI_Process()
ranged_timer.Start();
}
else if (!IsBotRanged() && GetLevel() < stop_melee_level) {
if (!GetMaxMeleeRange() || !RuleB(Bots, DisableSpecialAbilitiesAtMaxMelee)) {
else if (!IsBotRanged() && !stop_melee_level) {
if (
IsTaunting() ||
!GetMaxMeleeRange() ||
!RuleB(Bots, DisableSpecialAbilitiesAtMaxMelee)
) {
DoClassAttacks(tar);
}
@@ -3065,7 +3078,7 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
// For races with a fixed size
if (GetRace() == Race::LavaDragon || GetRace() == Race::Wurm || GetRace() == Race::GhostDragon) {
// size_mod = 60.0f;
size_mod = 60.0f;
}
else if (size_mod < 6.0f) {
size_mod = 8.0f;
@@ -3110,91 +3123,38 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
size_mod = (size_mod / 7.0f);
}
o.melee_distance_max = size_mod;
o.melee_distance_max = sqrt(size_mod);
if (!RuleB(Bots, UseFlatNormalMeleeRange)) {
bool is_two_hander = input.p_item && input.p_item->GetItem()->IsType2HWeapon();
bool is_shield = input.s_item && input.s_item->GetItem()->IsTypeShield();
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage();
bool is_two_hander = input.p_item && input.p_item->GetItem()->IsType2HWeapon();
bool is_shield = input.s_item && input.s_item->GetItem()->IsTypeShield();
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage();
switch (GetClass()) {
case Class::Warrior:
case Class::Paladin:
case Class::ShadowKnight:
o.melee_distance = (
is_two_hander ? o.melee_distance_max * 0.45f
: is_shield ? o.melee_distance_max * 0.35f
: o.melee_distance_max * 0.40f
);
break;
case Class::Necromancer:
case Class::Wizard:
case Class::Magician:
case Class::Enchanter:
o.melee_distance = (
is_two_hander ? o.melee_distance_max * 0.95f
: o.melee_distance_max * 0.75f
);
break;
case Class::Rogue:
o.melee_distance = (
input.behind_mob && is_backstab_weapon
? o.melee_distance_max * 0.35f
: o.melee_distance_max * 0.50f
);
break;
default:
o.melee_distance = (
is_two_hander ? o.melee_distance_max * 0.70f
: o.melee_distance_max * 0.50f
);
break;
}
o.melee_distance = sqrt(o.melee_distance);
o.melee_distance_max = sqrt(o.melee_distance_max);
if (IsTaunting()) { // Taunting bots
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerTauntingMeleeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperTauntingMeleeDistanceMultiplier);
}
else {
o.melee_distance_max = sqrt(o.melee_distance_max);
o.melee_distance = o.melee_distance_max * RuleR(Bots, NormalMeleeRangeDistance);
else if (IsBotRanged()) { // Archers/Throwers
float min_distance = RuleI(Combat, MinRangedAttackDist);
float max_distance = GetBotRangedValue();
float desired_range = GetBotDistanceRanged();
max_distance = (max_distance == 0 ? desired_range : max_distance); // stay ranged even if items/ammo aren't correct
o.melee_distance_min = std::max(min_distance, (desired_range / 2));
o.melee_distance = std::min(max_distance, desired_range);
}
else if (input.stop_melee_level) { // Casters
float desired_range = GetBotDistanceRanged();
if (o.melee_distance > RuleR(Bots, MaxDistanceForMelee)) {
o.melee_distance = RuleR(Bots, MaxDistanceForMelee);
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range / 2));
o.melee_distance = std::max((o.melee_distance_max * 1.25f), desired_range);
}
o.melee_distance_min = o.melee_distance * RuleR(Bots, PercentMinMeleeDistance);
if (IsTaunting()) {
o.melee_distance_min = o.melee_distance * RuleR(Bots, PercentTauntMinMeleeDistance);
o.melee_distance = o.melee_distance * RuleR(Bots, TauntNormalMeleeRangeDistance);
else if (GetMaxMeleeRange()) { // Melee bots set to max melee range
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMaxMeleeRangeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMaxMeleeRangeDistanceMultiplier);
}
bool is_stop_melee_level = GetLevel() >= input.stop_melee_level;
if (!IsTaunting() && !IsBotRanged() && !is_stop_melee_level && GetMaxMeleeRange()) {
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, PercentMinMaxMeleeRangeDistance);
o.melee_distance = o.melee_distance_max * RuleR(Bots, PercentMaxMeleeRangeDistance);
}
if (is_stop_melee_level && !IsBotRanged()) {
float desired_range = GetBotDistanceRanged();
o.melee_distance_min = std::max(o.melee_distance, (desired_range / 2));
o.melee_distance = std::max((o.melee_distance + 1), desired_range);
}
if (IsBotRanged()) {
float min_distance = RuleI(Combat, MinRangedAttackDist);
float max_distance = GetBotRangedValue();
float desired_range = GetBotDistanceRanged();
max_distance = (max_distance == 0 ? desired_range : max_distance); // stay ranged if set to ranged even if items/ammo aren't correct
o.melee_distance_min = std::max(min_distance, (desired_range / 2));
o.melee_distance = std::min(max_distance, desired_range);
else { // Regular melee
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMeleeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMeleeDistanceMultiplier);
}
o.at_combat_range = (input.target_distance <= o.melee_distance);
@@ -3215,14 +3175,17 @@ bool Bot::IsValidTarget(
return false;
}
SetHasLoS(DoLosChecks(tar));
bool invalid_target_state = false;
if (HOLDING ||
!tar->IsNPC() ||
(tar->IsMezzed() && !HasBotAttackFlag(tar)) ||
(!Charmed() && tar->GetUltimateOwner()->IsOfClientBotMerc()) ||
lo_distance > leash_distance ||
tar_distance > leash_distance ||
(!GetAttackingFlag() && !CheckLosCheat(tar) && !leash_owner->CheckLosCheat(tar)) ||
(!GetAttackingFlag() && !HasLoS()) ||
!IsAttackAllowed(tar)
) {
invalid_target_state = true;
@@ -3615,9 +3578,18 @@ void Bot::BotPullerProcess(Client* bot_owner, Raid* raid) {
void Bot::Depop() {
WipeHateList();
entity_list.RemoveFromHateLists(this);
RemoveAllAuras();
if (HasPet())
GetPet()->Depop();
Mob* bot_pet = GetPet();
if (bot_pet) {
if (bot_pet->Charmed()) {
bot_pet->BuffFadeByEffect(SE_Charm);
}
else {
bot_pet->Depop();
}
}
_botOwner = nullptr;
_botOwnerCharacterID = 0;
@@ -3740,13 +3712,6 @@ bool Bot::Spawn(Client* botCharacterOwner) {
}
}
MapSpellTypeLevels();
if (RuleB(Bots, RunSpellTypeChecksOnSpawn)) {
OwnerMessage("Running SpellType checks. There may be some spells that are mislabeled as incorrect. Use this as a loose guideline.");
CheckBotSpells(); //This runs through a series of checks and outputs any spells that are set to the wrong spell type in the database
}
if (IsBotRanged()) {
ChangeBotRangedWeapons(true);
}
@@ -9571,7 +9536,14 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool precheck
return false;
}
if (!BotHasEnoughMana(spell_id)) {
if (
!BotHasEnoughMana(spell_id) &&
(
!RuleB(Bots, FinishBuffing) ||
IsEngaged() ||
IsBotBuffSpellType(spell_type)
)
) {
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} due to !BotHasEnoughMana.'", GetCleanName(), GetSpellName(spell_id));
return false;
}
@@ -9766,7 +9738,7 @@ bool Bot::CastChecks(uint16 spell_id, Mob* tar, uint16 spell_type, bool precheck
)
)
&&
tar->CanBuffStack(spell_id, GetLevel(), true) < 0
tar->CanBuffStack(spell_id, GetLevel(), false) < 0
) {
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to !CanBuffStack.'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName());
return false;
@@ -9830,6 +9802,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
}
uint8 bot_class = GetClass();
auto spell = spells[spell_id];
switch (spell_type) {
case BotSpellTypes::Buff:
@@ -9853,7 +9826,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
case BotSpellTypes::SendHome:
if (
tar == this &&
spells[spell_id].target_type == ST_TargetsTarget
spell.target_type == ST_TargetsTarget
) {
LogBotSpellChecksDetail("{} says, 'Cancelling cast of {} on {} due to target_type checks. Using {}'", GetCleanName(), GetSpellName(spell_id), tar->GetCleanName(), GetSpellTargetType(spell_id));
return false;
@@ -9883,7 +9856,7 @@ bool Bot::CanCastSpellType(uint16 spell_type, uint16 spell_id, Mob* tar) {
}
if (
spells[spell_id].target_type == ST_Pet &&
(spell.target_type == ST_Pet || spell.target_type == ST_SummonedPet) &&
(
!tar->IsPet() ||
(
@@ -10626,6 +10599,8 @@ BotSpell Bot::GetSpellByHealType(uint16 spell_type, Mob* tar) {
return GetBestBotSpellForHealOverTime(this, tar, spell_type);
case BotSpellTypes::GroupHoTHeals:
return GetBestBotSpellForGroupHealOverTime(this, tar, spell_type);
default:
return BotSpell(); // Return an empty BotSpell if no valid spell type is found
}
}
@@ -10677,7 +10652,7 @@ int Bot::GetDefaultSetting(uint16 setting_category, uint16 setting_type, uint8 s
case BotSettingCategories::SpellTypeAnnounceCast:
return GetDefaultSpellTypeAnnounceCast(setting_type, stance);
default:
break;
return 0; // Default return value for unrecognized categories
}
}
@@ -10716,7 +10691,7 @@ int Bot::GetSetting(uint16 setting_category, uint16 setting_type) {
case BotSettingCategories::SpellTypeAnnounceCast:
return GetSpellTypeAnnounceCast(setting_type);
default:
break;
return 0; // Default return value for unrecognized categories
}
}
@@ -11443,7 +11418,7 @@ bool Bot::AttemptForcedCastSpell(Mob* tar, uint16 spell_id, bool is_disc) {
return false;
}
if (!DoLosChecks(tar)) {
if (!HasLoS() && !DoLosChecks(tar)) {
return false;
}
@@ -11606,18 +11581,265 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
return false;
}
if (IsBotSpellTypeDetrimental(spell_type) && !IsDetrimentalSpell(spell_id)) {
return false;
}
if (IsBotSpellTypeBeneficial(spell_type) && !IsBeneficialSpell(spell_id)) {
return false;
}
auto spell = spells[spell_id];
std::string teleport_zone = spell.teleport_zone;
switch (spell_type) {
case BotSpellTypes::Buff:
case BotSpellTypes::PetBuffs:
if (
IsResistanceOnlySpell(spell_id) ||
IsDamageShieldOnlySpell(spell_id) ||
IsDamageShieldAndResistSpell(spell_id)
) {
return false;
case BotSpellTypes::Nuke:
if (IsAnyNukeOrStunSpell(spell_id) && !IsStunSpell(spell_id)) {
return true;
}
return true;
return false;
case BotSpellTypes::RegularHeal:
case BotSpellTypes::PetRegularHeals:
if (
IsAnyHealSpell(spell_id) &&
!IsVeryFastHealSpell(spell_id) &&
!IsFastHealSpell(spell_id) &&
!IsCompleteHealSpell(spell_id) &&
!IsHealOverTimeSpell(spell_id) &&
!IsBuffSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::Root:
case BotSpellTypes::AERoot:
if (IsDetrimentalSpell(spell_id) && IsEffectInSpell(spell_id, SE_Root)) {
return true;
}
return false;
case BotSpellTypes::Buff:
case BotSpellTypes::PreCombatBuff:
case BotSpellTypes::PetBuffs:
if (
IsBuffSpell(spell_id) &&
IsBeneficialSpell(spell_id) &&
!IsBardSong(spell_id) &&
!IsResistanceOnlySpell(spell_id) &&
!IsDamageShieldOnlySpell(spell_id) &&
!IsDamageShieldAndResistSpell(spell_id)
) {
if (
spell_type != BotSpellTypes::PetBuffs &&
(spell.target_type == ST_Pet || spell.target_type == ST_SummonedPet)
) {
return false;
}
return true;
}
return false;
case BotSpellTypes::Escape:
if (IsEscapeSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Pet:
if (IsSummonPetSpell(spell_id) || IsEffectInSpell(spell_id, SE_TemporaryPets)) {
return true;
}
return false;
case BotSpellTypes::Lifetap:
case BotSpellTypes::AELifetap:
if (IsDetrimentalSpell(spell_id) && IsLifetapSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Snare:
case BotSpellTypes::AESnare:
if (IsDetrimentalSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) {
return true;
}
return false;
case BotSpellTypes::DOT:
case BotSpellTypes::AEDoT:
if (
IsDetrimentalSpell(spell_id) &&
(IsStackableDOT(spell_id) || IsDamageOverTimeSpell(spell_id))
) {
return true;
}
return false;
case BotSpellTypes::Dispel:
case BotSpellTypes::AEDispel:
if (IsDispelSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::InCombatBuff:
if (
!IsBardSong(spell_id) &&
(
(IsSelfConversionSpell(spell_id) && spell.buff_duration < 1) ||
(spell_type == BotSpellTypes::InCombatBuff && IsAnyBuffSpell(spell_id))
)
) {
return true;
}
return false;
case BotSpellTypes::Mez:
case BotSpellTypes::AEMez:
if (IsMesmerizeSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Charm:
if (IsCharmSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Slow:
case BotSpellTypes::AESlow:
if (IsDetrimentalSpell(spell_id) && IsSlowSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Debuff:
case BotSpellTypes::AEDebuff:
if (
IsDebuffSpell(spell_id) &&
!IsHateReduxSpell(spell_id) &&
!IsHateSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::Cure:
case BotSpellTypes::GroupCures:
case BotSpellTypes::PetCures:
if (IsCureSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Resurrect:
if (IsEffectInSpell(spell_id, SE_Revive)) {
return true;
}
return false;
case BotSpellTypes::HateRedux:
if (IsHateReduxSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::InCombatBuffSong:
case BotSpellTypes::OutOfCombatBuffSong:
case BotSpellTypes::PreCombatBuffSong:
if (
IsBuffSpell(spell_id) &&
IsBeneficialSpell(spell_id) &&
IsBardSong(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::Fear:
case BotSpellTypes::AEFear:
if (IsFearSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Stun:
case BotSpellTypes::AEStun:
if (IsDetrimentalSpell(spell_id) && IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::HateLine:
case BotSpellTypes::AEHateLine:
if (IsDetrimentalSpell(spell_id) && IsHateSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::CompleteHeal:
case BotSpellTypes::GroupCompleteHeals:
case BotSpellTypes::PetCompleteHeals:
if (IsCompleteHealSpell(spell_id) || IsGroupCompleteHealSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::FastHeals:
case BotSpellTypes::PetFastHeals:
if (IsFastHealSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::VeryFastHeals:
case BotSpellTypes::PetVeryFastHeals:
if (IsVeryFastHealSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::GroupHeals:
if (IsRegularGroupHealSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::HoTHeals:
case BotSpellTypes::GroupHoTHeals:
case BotSpellTypes::PetHoTHeals:
if (IsHealOverTimeSpell(spell_id) || IsGroupHealOverTimeSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::AENukes:
if (
IsDetrimentalSpell(spell_id) &&
!IsAERainSpell(spell_id) &&
!IsPBAENukeSpell(spell_id) &&
!IsStunSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::AERains:
if (IsAERainNukeSpell(spell_id) && !IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::PBAENuke:
if (IsPBAENukeSpell(spell_id) && !IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::ResistBuffs:
case BotSpellTypes::PetResistBuffs:
if (IsResistanceBuffSpell(spell_id)) {
@@ -11627,44 +11849,7 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
return false;
case BotSpellTypes::DamageShields:
case BotSpellTypes::PetDamageShields:
if (IsEffectInSpell(spell_id, SE_DamageShield)) {
return true;
}
return false;
case BotSpellTypes::PBAENuke:
if (
IsPBAENukeSpell(spell_id) &&
!IsStunSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::AERains:
if (
IsAERainNukeSpell(spell_id) &&
!IsStunSpell(spell_id)
) {
return true;
}
return false;
case BotSpellTypes::AEStun:
case BotSpellTypes::Stun:
if (IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::AENukes:
case BotSpellTypes::Nuke:
if (!IsStunSpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Lull:
if (IsHarmonySpell(spell_id)) {
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_DamageShield)) {
return true;
}
@@ -11672,13 +11857,18 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
case BotSpellTypes::Teleport:
if (
IsBeneficialSpell(spell_id) &&
(
IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate)
)
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
) {
return true;
}
return false;
case BotSpellTypes::Lull:
case BotSpellTypes::AELull:
if (IsHarmonySpell(spell_id)) {
return true;
}
return false;
case BotSpellTypes::Succor:
if (
@@ -11702,25 +11892,21 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
return false;
case BotSpellTypes::Levitate:
if (
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_Levitate)
) {
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_Levitate)) {
return true;
}
return false;
case BotSpellTypes::Rune:
if (
IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) ||
IsEffectInSpell(spell_id, SE_Rune)
if (IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_AbsorbMagicAtt) || IsEffectInSpell(spell_id, SE_Rune))
) {
return true;
}
return false;
case BotSpellTypes::WaterBreathing:
if (IsEffectInSpell(spell_id, SE_WaterBreathing)) {
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_WaterBreathing)) {
return true;
}
@@ -11728,29 +11914,22 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
case BotSpellTypes::Size:
if (
IsBeneficialSpell(spell_id) &&
(
IsEffectInSpell(spell_id, SE_ModelSize) ||
IsEffectInSpell(spell_id, SE_ChangeHeight)
)
(IsEffectInSpell(spell_id, SE_ModelSize) || IsEffectInSpell(spell_id, SE_ChangeHeight))
) {
return true;
}
return false;
case BotSpellTypes::Invisibility:
if (
IsEffectInSpell(spell_id, SE_SeeInvis) ||
IsInvisibleSpell(spell_id)
if (IsBeneficialSpell(spell_id) &&
(IsEffectInSpell(spell_id, SE_SeeInvis) ||IsInvisibleSpell(spell_id))
) {
return true;
}
return false;
case BotSpellTypes::MovementSpeed:
if (
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_MovementSpeed)
) {
if (IsBeneficialSpell(spell_id) && IsEffectInSpell(spell_id, SE_MovementSpeed)) {
return true;
}
@@ -11758,7 +11937,13 @@ bool Bot::IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id) {
case BotSpellTypes::SendHome:
if (
IsBeneficialSpell(spell_id) &&
IsEffectInSpell(spell_id, SE_GateToHomeCity)
(
IsEffectInSpell(spell_id, SE_GateToHomeCity) ||
(
teleport_zone.compare("") &&
(IsEffectInSpell(spell_id, SE_Teleport) || IsEffectInSpell(spell_id, SE_Translocate))
)
)
) {
return true;
}
@@ -11846,77 +12031,64 @@ void Bot::DoCombatPositioning(
bool behind_mob,
bool front_mob
) {
if (HasTargetReflection()) {
if (!IsTaunting() && !tar->IsFeared() && !tar->IsStunned()) {
if (TryEvade(tar)) {
return;
}
}
else if (tar->IsRooted() && !IsTaunting()) { // Move non-taunters out of range - Above already checks if bot is targeted, otherwise they would stay
if (tar_distance <= melee_distance_max) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 2), GetBehindMob(), false)) {
RunToGoalWithJitter(Goal);
if (!tar->IsFeared()) {
bool is_too_close = tar_distance < melee_distance_min;
bool los_adjust = !HasRequiredLoSForPositioning(tar);
if (tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range
bool rooted_adjust = tar_distance <= melee_distance_max && HasTargetReflection();
if (rooted_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 1.25f), GetBehindMob(), !GetBehindMob())) {
RunToGoalWithJitter(Goal);
return;
}
}
}
else if (tar_distance < melee_distance_min || (!front_mob && IsTaunting())) { // Back up any bots that are too close
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) {
RunToGoalWithJitter(Goal);
return;
}
}
}
else {
if (!tar->IsFeared()) {
else {
if (IsTaunting()) { // Taunting adjustments
Mob* mob_tar = tar->GetTarget();
bool taunting_adjust = (!front_mob || is_too_close || los_adjust);
if (!mob_tar) {
DoFaceCheckNoJitter(tar);
return;
}
if (RuleB(Bots, TauntingBotsFollowTopHate)) { // If enabled, taunting bots will stick to top hate
if (Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate)) {
Goal = mob_tar->GetPosition();
RunToGoalWithJitter(Goal);
return;
}
}
else { // Otherwise, stick to any other bots that are taunting
if (mob_tar->IsBot() && mob_tar->CastToBot()->IsTaunting() && (Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate))) {
Goal = mob_tar->GetPosition();
if (taunting_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, true)) {
RunToGoalWithJitter(Goal);
return;
}
}
}
else if (tar_distance < melee_distance_min || (GetBehindMob() && !behind_mob) || (IsTaunting() && !front_mob) || !HasRequiredLoSForPositioning(tar)) { // Regular adjustment
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) {
RunToGoalWithJitter(Goal);
else {
if (tar->IsEnraged() && !stop_melee_level && !IsBotRanged()) { // Move non-taunting melee bots behind target during enrage
bool enraged_adjust = !behind_mob || is_too_close || los_adjust;
return;
if (enraged_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) {
RunToGoalWithJitter(Goal);
return;
}
}
}
}
else if (tar->IsEnraged() && !IsTaunting() && !stop_melee_level && !behind_mob) { // Move non-taunting melee bots behind target during enrage
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) {
RunToGoalWithJitter(Goal);
else { // Regular adjustments
bool regular_adjust =
is_too_close ||
los_adjust ||
(!GetBehindMob() && !front_mob) ||
(GetBehindMob() && !behind_mob);
return;
if (regular_adjust) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), !GetBehindMob())) {
RunToGoalWithJitter(Goal);
return;
}
}
}
}
}
}
DoFaceCheckNoJitter(tar);
return;
}
bool Bot::PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behind_only, bool front_only, bool bypass_los) {
@@ -12013,7 +12185,11 @@ bool Bot::RequiresLoSForPositioning() {
}
for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; ++i) {
if (IsBotSpellTypeDetrimental(i) && !GetSpellTypeHold(i)) {
if (IsHealBotSpellType(i) || i == BotSpellTypes::PBAENuke) {
continue;
}
if (!GetSpellTypeHold(i)) {
return true;
}
}
@@ -12026,7 +12202,7 @@ bool Bot::HasRequiredLoSForPositioning(Mob* tar) {
return true;
}
if (RequiresLoSForPositioning() && !DoLosChecks(tar)) {
if (RequiresLoSForPositioning() && !HasLoS()) {
return false;
}
@@ -12041,10 +12217,6 @@ bool Bot::HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob*
for (auto& close_mob : caster->m_close_mobs) {
Mob* m = close_mob.second;
if (tar == m) {
continue;
}
switch (spell_type) {
case BotSpellTypes::AELull:
if (m->GetSpecialAbility(SpecialAbility::PacifyImmunity)) {
@@ -12136,8 +12308,6 @@ bool Bot::HasValidAETarget(Bot* caster, uint16 spell_id, uint16 spell_type, Mob*
return false;
}
SetHasLoS(true);
return true;
}
+2 -8
View File
@@ -235,8 +235,7 @@ static std::map<uint16, std::string> botSubType_names = {
struct CombatRangeInput {
Mob* target;
float target_distance;
bool behind_mob;
uint8 stop_melee_level;
bool stop_melee_level;
const EQ::ItemInstance* p_item;
const EQ::ItemInstance* s_item;
};
@@ -684,12 +683,9 @@ public:
void SetSitManaPct(uint8 value) { _SitManaPct = value; }
// Spell lists
void CheckBotSpells();
void MapSpellTypeLevels();
const std::map<int32_t, std::map<int32_t, BotSpellTypesByClass>>& GetCommandedSpellTypesMinLevels() { return commanded_spells_min_level; }
std::list<BotSpellTypeOrder> GetSpellTypesPrioritized(uint8 priority_type);
static uint16 GetParentSpellType(uint16 spell_type);
bool IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id);
static bool IsValidSpellTypeBySpellID(uint16 spell_type, uint16 spell_id);
inline uint16 GetCastedSpellType() const { return _castedSpellType; }
void SetCastedSpellType(uint16 spell_type);
bool IsValidSpellTypeSubType(uint16 spell_type, uint16 sub_type, uint16 spell_id);
@@ -1153,8 +1149,6 @@ protected:
std::vector<BotSpells> AIBot_spells_enforced;
std::unordered_map<uint16, std::vector<BotSpells_wIndex>> AIBot_spells_by_type;
std::map<int32_t, std::map<int32_t, BotSpellTypesByClass>> commanded_spells_min_level;
std::vector<BotTimer> bot_timers;
std::vector<BotBlockedBuffs> bot_blocked_buffs;
+11 -3
View File
@@ -57,6 +57,7 @@
#include "water_map.h"
#include "worldserver.h"
#include "mob.h"
#include "bot_database.h"
#include <fmt/format.h>
@@ -219,6 +220,13 @@ int bot_command_init(void)
std::vector<std::pair<std::string, uint8>> injected_bot_command_settings;
std::vector<std::string> orphaned_bot_command_settings;
if (RuleB(Bots, RunSpellTypeChecksOnBoot)) {
LogBotSpellTypeChecks("Running SpellType checks. There may be some spells that are mislabeled as incorrect. Use this as a loose guideline.");
database.botdb.CheckBotSpells();
}
database.botdb.MapCommandedSpellTypeMinLevels();
for (auto bcs_iter : bot_command_settings) {
auto bcl_iter = bot_command_list.find(bcs_iter.first);
@@ -796,10 +804,10 @@ void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type)
}
}
auto& spell_map = bot->GetCommandedSpellTypesMinLevels();
auto spell_map = database.botdb.GetCommandedSpellTypesMinLevels();
if (spell_map.empty()) {
bot_owner->Message(Chat::Yellow, "No bots are capable of casting this spell type");
bot_owner->Message(Chat::Yellow, "No bots are capable of casting this spell type.");
return;
}
@@ -810,7 +818,7 @@ void helper_send_usage_required_bots(Client *bot_owner, uint16 spell_type)
auto spell_type_itr = spell_map.find(spell_type);
auto class_itr = spell_type_itr->second.find(i);
const auto& spell_info = class_itr->second;
if (spell_info.min_level < UINT8_MAX) {
found = true;
+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;
}
+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;
+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;
+1 -1
View File
@@ -48,7 +48,7 @@ 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;
+206
View File
@@ -36,6 +36,7 @@
#include "../common/repositories/bot_pet_buffs_repository.h"
#include "../common/repositories/bot_pet_inventories_repository.h"
#include "../common/repositories/bot_spell_casting_chances_repository.h"
#include "../common/repositories/bot_spells_entries_repository.h"
#include "../common/repositories/bot_settings_repository.h"
#include "../common/repositories/bot_stances_repository.h"
#include "../common/repositories/bot_timers_repository.h"
@@ -2521,3 +2522,208 @@ bool BotDatabase::DeleteBotBlockedBuffs(const uint32 bot_id)
return true;
}
void BotDatabase::CheckBotSpells() {
auto spell_list = BotSpellsEntriesRepository::All(content_db);
uint16 spell_id;
SPDat_Spell_Struct spell;
for (const auto& s : spell_list) {
if (!IsValidSpell(s.spell_id)) {
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
continue;
}
spell = spells[s.spell_id];
spell_id = spell.id;
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) {
LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id);
}
else {
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}.",
GetSpellName(spell_id),
spell_id,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id,
s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
spell_id,
s.npc_spells_id,
GetSpellName(spell_id),
spell_id,
s.minlevel,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}.",
GetSpellName(spell_id),
spell_id,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id,
s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]",
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
spell_id,
s.npc_spells_id,
GetSpellName(spell_id),
spell_id,
s.minlevel,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}.",
GetSpellName(spell_id),
spell_id,
spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)],
GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX),
s.npc_spells_id,
s.maxlevel
);
}
}
uint16 correct_type = GetCorrectBotSpellType(s.type, spell_id);
if (RuleB(Bots, UseParentSpellTypeForChecks)) {
uint16 parent_type = Bot::GetParentSpellType(correct_type);
if (s.type == parent_type || s.type == correct_type) {
continue;
}
if (correct_type != parent_type) {
correct_type = parent_type;
}
}
else {
if (IsPetBotSpellType(s.type)) {
correct_type = GetPetBotSpellType(correct_type);
}
}
if (IsPetBotSpellType(correct_type) && (spell.target_type != ST_Pet && spell.target_type != ST_SummonedPet)) {
correct_type = Bot::GetParentSpellType(correct_type);
}
if (correct_type == s.type) {
continue;
}
if (correct_type == UINT16_MAX) {
LogBotSpellTypeChecks(
"{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown.",
GetSpellName(spell_id),
spell_id,
Bot::GetSpellTypeNameByID(s.type),
s.type
);
}
else {
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]",
GetSpellName(spell_id),
spell_id,
Bot::GetSpellTypeNameByID(s.type),
s.type,
Bot::GetSpellTypeNameByID(correct_type),
correct_type
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]",
correct_type,
spell_id,
GetSpellName(spell_id),
spell_id,
Bot::GetSpellTypeNameByID(s.type),
s.type,
Bot::GetSpellTypeNameByID(correct_type),
correct_type
);
}
}
}
void BotDatabase::MapCommandedSpellTypeMinLevels() {
commanded_spell_type_min_levels.clear();
auto start = std::min({ BotSpellTypes::START, BotSpellTypes::COMMANDED_START, BotSpellTypes::DISCIPLINE_START });
auto end = std::max({ BotSpellTypes::END, BotSpellTypes::COMMANDED_END, BotSpellTypes::DISCIPLINE_END });
for (int i = start; i <= end; ++i) {
if (!Bot::IsValidBotSpellType(i)) {
continue;
}
for (int x = Class::Warrior; x <= Class::Berserker; ++x) {
commanded_spell_type_min_levels[i][x] = {UINT8_MAX, "" };
}
}
auto spell_list = BotSpellsEntriesRepository::All(content_db);
for (const auto& s : spell_list) {
if (!IsValidSpell(s.spell_id)) {
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
continue;
}
auto spell = spells[s.spell_id];
if (spell.target_type == ST_Self) {
continue;
}
int32_t bot_class = s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX;
if (
!EQ::ValueWithin(bot_class, Class::Warrior, Class::Berserker) ||
!Bot::IsValidBotSpellType(s.type)
) {
continue;
}
for (int i = start; i <= end; ++i) {
if (s.minlevel > commanded_spell_type_min_levels[i][bot_class].min_level) {
continue;
}
if (
i > BotSpellTypes::PARENT_TYPE_END &&
i != s.type &&
Bot::GetParentSpellType(i) != s.type
) {
continue;
}
if (!Bot::IsValidSpellTypeBySpellID(i, s.spell_id)) {
continue;
}
if (s.minlevel < commanded_spell_type_min_levels[i][bot_class].min_level) {
commanded_spell_type_min_levels[i][bot_class].min_level = s.minlevel;
commanded_spell_type_min_levels[i][bot_class].description = StringFormat(
"%s [#%u] - Level %u",
GetClassIDName(bot_class),
bot_class,
s.minlevel
);
}
}
}
}
+9 -1
View File
@@ -24,7 +24,7 @@
#include <list>
#include <map>
#include <vector>
#include "bot_structs.h"
class Bot;
class Client;
@@ -130,6 +130,10 @@ public:
bool SaveBotSettings(Mob* m);
bool DeleteBotSettings(const uint32 bot_id);
void CheckBotSpells();
void MapCommandedSpellTypeMinLevels();
std::map<int32_t, std::map<int32_t, BotSpellTypesByClass>> GetCommandedSpellTypesMinLevels() { return commanded_spell_type_min_levels; }
/* Bot group functions */
bool LoadGroupedBotsByGroupID(const uint32 owner_id, const uint32 group_id, std::list<uint32>& group_list);
@@ -211,6 +215,10 @@ public:
private:
std::string query;
protected:
std::map<int32_t, std::map<int32_t, BotSpellTypesByClass>> commanded_spell_type_min_levels;
};
#endif
+89 -249
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)
@@ -1433,11 +1465,10 @@ Mob* Bot::GetFirstIncomingMobToMez(Bot* caster, int16 spell_id, uint16 spell_typ
if (caster && caster->GetOwner()) {
int spell_range = caster->GetActSpellRange(spell_id, spells[spell_id].range);
int spell_ae_range = caster->GetAOERange(spell_id);
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)
@@ -2865,181 +2883,3 @@ BotSpell Bot::GetBestBotSpellForCharm(Bot* caster, Mob* target, uint16 spell_typ
return result;
}
void Bot::CheckBotSpells() {
auto spell_list = BotSpellsEntriesRepository::All(content_db);
uint16 spell_id;
SPDat_Spell_Struct spell;
uint16 correct_type;
uint16 parent_type;
for (const auto& s : spell_list) {
if (!IsValidSpell(s.spell_id)) {
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
continue;
}
spell = spells[s.spell_id];
spell_id = spell.id;
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] >= 255) {
LogBotSpellTypeChecks("{} [#{}] is not usable by a {} [#{}].", GetSpellName(spell_id), spell_id, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX), s.npc_spells_id);
}
else {
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the min level is currently set to {}."
, GetSpellName(spell_id)
, spell_id
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
, s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]"
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, spell_id
, s.npc_spells_id
, GetSpellName(spell_id)
, spell_id
, s.minlevel
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] < s.minlevel) {
LogBotSpellTypeChecks("{} [#{}] could be used starting at level {} for a {} [#{}] instead of the current min level of {}."
, GetSpellName(spell_id)
, spell_id
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
, s.minlevel
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `minlevel` = {} WHERE `spellid` = {} AND `npc_spells_id` = {}; -- {} [#{}] from minlevel {} to {} for {} [#{}]"
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, spell_id
, s.npc_spells_id
, GetSpellName(spell_id)
, spell_id
, s.minlevel
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
);
}
if (spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)] > s.maxlevel) {
LogBotSpellTypeChecks("{} [#{}] is not usable until level {} for a {} [#{}] and the max level is currently set to {}."
, GetSpellName(spell_id)
, spell_id
, spell.classes[s.npc_spells_id - (BOT_CLASS_BASE_ID_PREFIX + 1)]
, GetClassIDName(s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX)
, s.npc_spells_id
, s.maxlevel
);
}
}
correct_type = GetCorrectBotSpellType(s.type, spell_id);
parent_type = GetParentSpellType(correct_type);
if (RuleB(Bots, UseParentSpellTypeForChecks)) {
if (s.type == parent_type || s.type == correct_type) {
continue;
}
}
else {
if (IsPetBotSpellType(s.type)) {
correct_type = GetPetBotSpellType(correct_type);
}
}
if (correct_type == s.type) {
continue;
}
if (correct_type == UINT16_MAX) {
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] but the correct type is unknown."
, GetSpellName(spell_id)
, spell_id
, GetSpellTypeNameByID(s.type)
, s.type
);
}
else {
LogBotSpellTypeChecks("{} [#{}] is incorrect. It is currently set as {} [#{}] and should be {} [#{}]"
, GetSpellName(spell_id)
, spell_id
, GetSpellTypeNameByID(s.type)
, s.type
, GetSpellTypeNameByID(correct_type)
, correct_type
);
LogBotSpellTypeChecksDetail("UPDATE bot_spells_entries SET `type` = {} WHERE `spell_id` = {}; -- {} [#{}] from {} [#{}] to {} [#{}]"
, correct_type
, spell_id
, GetSpellName(spell_id)
, spell_id
, GetSpellTypeNameByID(s.type)
, s.type
, GetSpellTypeNameByID(correct_type)
, correct_type
);
}
}
}
void Bot::MapSpellTypeLevels() {
commanded_spells_min_level.clear();
auto start = std::min({ BotSpellTypes::START, BotSpellTypes::COMMANDED_START, BotSpellTypes::DISCIPLINE_START });
auto end = std::max({ BotSpellTypes::END, BotSpellTypes::COMMANDED_END, BotSpellTypes::DISCIPLINE_END });
for (int i = start; i <= end; ++i) {
if (!Bot::IsValidBotSpellType(i)) {
continue;
}
for (int x = Class::Warrior; x <= Class::Berserker; ++x) {
commanded_spells_min_level[i][x] = { UINT8_MAX, "" };
}
}
auto spell_list = BotSpellsEntriesRepository::All(content_db);
for (const auto& s : spell_list) {
if (!IsValidSpell(s.spell_id)) {
LogBotSpellTypeChecks("{} is an invalid spell", s.spell_id);
continue;
}
uint16_t spell_type = s.type;
int32_t bot_class = s.npc_spells_id - BOT_CLASS_BASE_ID_PREFIX;
uint8_t min_level = s.minlevel;
if (
!EQ::ValueWithin(bot_class, Class::Warrior, Class::Berserker) ||
!Bot::IsValidBotSpellType(spell_type)
) {
continue;
}
auto& spell_info = commanded_spells_min_level[spell_type][bot_class];
if (min_level < spell_info.min_level) {
spell_info.min_level = min_level;
spell_info.description = StringFormat(
"%s [#%u]: Level %u",
GetClassIDName(bot_class),
bot_class,
min_level
);
}
}
}
+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();
}
+247
View File
@@ -0,0 +1,247 @@
#include "../../common/http/httplib.h"
#include "../../common/eqemu_logsys.h"
#include "../../common/platform.h"
#include "../../zone.h"
#include "../../client.h"
#include "../../common/net/eqstream.h"
extern Zone *zone;
void ZoneCLI::TestDataBuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
{
if (cmd[{"-h", "--help"}]) {
return;
}
uint32 break_length = 50;
int failed_count = 0;
LogSys.SilenceConsoleLogging();
// boot shell zone for testing
Zone::Bootup(ZoneID("qrg"), 0, false);
zone->StopShutdownTimer();
entity_list.Process();
entity_list.MobProcess();
LogSys.EnableConsoleLogging();
std::cout << "===========================================\n";
std::cout << "\uFE0F> Running DataBuckets Tests...\n";
std::cout << "===========================================\n\n";
Client *client = new Client();
// Basic Key-Value Set/Get
client->DeleteBucket("basic_key");
client->SetBucket("basic_key", "simple_value");
std::string value = client->GetBucket("basic_key");
RunTest("Basic Key-Value Set/Get", "simple_value", value);
// Overwriting a Key
client->SetBucket("basic_key", "new_value");
value = client->GetBucket("basic_key");
RunTest("Overwriting a Key", "new_value", value);
// Deleting a Key
client->DeleteBucket("basic_key");
value = client->GetBucket("basic_key");
RunTest("Deleting a Key", "", value);
// Setting a Key with an Expiration
client->SetBucket("expiring_key", "expires_soon", "S1");
value = client->GetBucket("expiring_key");
RunTest("Setting a Key with an Expiration", "expires_soon", value);
// Ensure Expired Key is Deleted
std::this_thread::sleep_for(std::chrono::seconds(2));
value = client->GetBucket("expiring_key");
RunTest("Ensure Expired Key is Deleted", "", value);
// Cache Read/Write Consistency
client->SetBucket("cache_key", "cached_value");
value = client->GetBucket("cache_key");
RunTest("Cache Read/Write Consistency", "cached_value", value);
// Cache Clears on Key Deletion
client->DeleteBucket("cache_key");
value = client->GetBucket("cache_key");
RunTest("Cache Clears on Key Deletion", "", value);
// Setting a Full JSON String
client->SetBucket("json_key", R"({"key1":"value1","key2":"value2"})");
value = client->GetBucket("json_key");
RunTest("Setting a Full JSON String", R"({"key1":"value1","key2":"value2"})", value);
// Overwriting JSON with a Simple String
client->SetBucket("json_key", "string_value");
value = client->GetBucket("json_key");
RunTest("Overwriting JSON with a Simple String", "string_value", value);
// Deleting Non-Existent Key
client->DeleteBucket("non_existent_key");
value = client->GetBucket("non_existent_key");
RunTest("Deleting Non-Existent Key", "", value);
// Basic Key-Value Storage**
client->DeleteBucket("simple_key"); // Reset
client->SetBucket("simple_key", "simple_value");
value = client->GetBucket("simple_key");
RunTest("Basic Key-Value Set/Get", "simple_value", value);
// Nested Key Storage**
client->DeleteBucket("nested");
client->SetBucket("nested.test1", "value1");
client->SetBucket("nested.test2", "value2");
value = client->GetBucket("nested");
RunTest("Nested Key Set/Get", R"({"test1":"value1","test2":"value2"})", value);
// Prevent Overwriting Objects**
client->DeleteBucket("nested");
client->SetBucket("nested.test1.a", "value1");
client->SetBucket("nested.test2.a", "value2");
client->SetBucket("nested.test2", "new_value"); // Should be **rejected**
value = client->GetBucket("nested");
RunTest("Prevent Overwriting Objects", R"({"test1":{"a":"value1"},"test2":{"a":"value2"}})", value);
// Deleting a Specific Nested Key**
client->DeleteBucket("nested");
client->SetBucket("nested.test1", "value1");
client->SetBucket("nested.test2", "value2");
client->DeleteBucket("nested.test1");
value = client->GetBucket("nested");
RunTest("Delete Nested Key", R"({"test2":"value2"})", value);
// Deleting the Entire Parent Key**
client->DeleteBucket("nested");
value = client->GetBucket("nested");
RunTest("Delete Parent Key", "", value);
// Expiration is Ignored for Nested Keys**
client->DeleteBucket("exp_test");
client->SetBucket("exp_test.nested", "data", "S20"); // Expiration ignored
value = client->GetBucket("exp_test");
RunTest("Expiration Ignored for Nested Keys", R"({"nested":"data"})", value);
// Cache Behavior**
client->DeleteBucket("cache_test");
client->SetBucket("cache_test", "cache_value");
value = client->GetBucket("cache_test");
RunTest("Cache Read/Write Consistency", "cache_value", value);
// Ensure Deleting Parent Key Clears Cache**
client->DeleteBucket("cache_test");
value = client->GetBucket("cache_test");
RunTest("Cache Clears on Parent Delete", "", value);
// Setting an Entire JSON Object**
client->DeleteBucket("full_json");
client->SetBucket("full_json", R"({"key1":"value1","key2":{"subkey":"subvalue"}})");
value = client->GetBucket("full_json");
RunTest("Set and Retrieve Full JSON Structure", R"({"key1":"value1","key2":{"subkey":"subvalue"}})", value);
// Partial Nested Key Deletion within JSON**
client->DeleteBucket("full_json");
client->SetBucket("full_json", R"({"key1":"value1","key2":{"subkey":"subvalue"}})");
client->DeleteBucket("full_json.key2");
value = client->GetBucket("full_json");
RunTest("Delete Nested Key within JSON", R"({"key1":"value1"})", value);
// Ensure Object Protection on Overwrite Attempt**
client->DeleteBucket("complex");
client->SetBucket("complex.nested.obj1", "data1");
client->SetBucket("complex.nested.obj2", "data2");
client->SetBucket("complex.nested", "overwrite_attempt"); // Should be rejected
value = client->GetBucket("complex");
RunTest("Ensure Object Protection on Overwrite Attempt", R"({"nested":{"obj1":"data1","obj2":"data2"}})", value);
// Deleting Non-Existent Key Doesn't Break Existing Data**
client->DeleteBucket("complex");
client->SetBucket("complex.nested.obj1", "data1");
client->SetBucket("complex.nested.obj2", "data2");
client->DeleteBucket("does_not_exist"); // Should do nothing
value = client->GetBucket("complex");
RunTest("Deleting Non-Existent Key Doesn't Break Existing Data", R"({"nested":{"obj1":"data1","obj2":"data2"}})", value);
// Get nested key value one level up **
client->DeleteBucket("complex");
client->SetBucket("complex.nested.obj1", "data1");
client->SetBucket("complex.nested.obj2", "data2");
value = client->GetBucket("complex.nested");
RunTest("Get nested key value", R"({"obj1":"data1","obj2":"data2"})", value);
// Get nested key value deep **
client->DeleteBucket("complex");
client->SetBucket("complex.nested.obj1", "data1");
client->SetBucket("complex.nested.obj2", "data2");
value = client->GetBucket("complex.nested.obj2");
RunTest("Get nested key value deep", R"(data2)", value);
// Retrieve Nested Key from Plain String**
client->DeleteBucket("plain_string");
client->SetBucket("plain_string", "some_value");
value = client->GetBucket("plain_string.nested");
RunTest("Retrieve Nested Key from Plain String", "", value);
// Store and Retrieve JSON Array**
client->DeleteBucket("json_array");
client->SetBucket("json_array", R"(["item1", "item2"])");
value = client->GetBucket("json_array");
RunTest("Store and Retrieve JSON Array", R"(["item1", "item2"])", value);
// // Prevent Overwriting Array with Object**
// client->DeleteBucket("json_array");
// client->SetBucket("json_array", R"(["item1", "item2"])");
// client->SetBucket("json_array.item", "new_value"); // Should be rejected
// value = client->GetBucket("json_array");
// RunTest("Prevent Overwriting Array with Object", R"(["item1", "item2"])", value);
// Retrieve Non-Existent Nested Key**
client->DeleteBucket("nested_partial");
client->SetBucket("nested_partial.level1", R"({"exists": "yes"})");
value = client->GetBucket("nested_partial.level1.non_existent");
RunTest("Retrieve Non-Existent Nested Key", "", value);
// Overwriting Parent Key Deletes Children**
client->DeleteBucket("nested_override");
client->SetBucket("nested_override.child", "data");
client->SetBucket("nested_override", "new_parent_value"); // Should remove `child`
value = client->GetBucket("nested_override");
RunTest("Overwriting Parent Key Deletes Children", "new_parent_value", value);
// Store and Retrieve Empty JSON Object**
client->DeleteBucket("empty_json");
client->SetBucket("empty_json", R"({})");
value = client->GetBucket("empty_json");
RunTest("Store and Retrieve Empty JSON Object", R"({})", value);
// Store and Retrieve JSON String**
client->DeleteBucket("json_string");
client->SetBucket("json_string", R"("this is a string")");
value = client->GetBucket("json_string");
RunTest("Store and Retrieve JSON String", R"("this is a string")", value);
// Deeply Nested Key Retrieval**
client->DeleteBucket("deep_nested");
client->SetBucket("deep_nested.level1.level2.level3.level4.level5", "final_value");
value = client->GetBucket("deep_nested.level1.level2.level3.level4.level5");
RunTest("Deeply Nested Key Retrieval", "final_value", value);
// Setting a Key with an Expiration
client->SetBucket("nested_expire.test.test", "shouldnt_expire", "S1");
value = client->GetBucket("nested_expire");
std::this_thread::sleep_for(std::chrono::seconds(2));
RunTest("Setting a nested key with an expiration protection test", R"({"test":{"test":"shouldnt_expire"}})", value);
// Delete Deep Nested Key Keeps Parent**
// client->DeleteBucket("deep_nested");
// client->SetBucket("deep_nested.level1.level2.level3", R"({"key": "value"})");
// client->DeleteBucket("deep_nested.level1.level2.level3.key");
// value = client->GetBucket("deep_nested.level1.level2.level3");
// RunTest("Delete Deep Nested Key Keeps Parent", "{}", value);
std::cout << "\n===========================================\n";
std::cout << "✅ All DataBucket Tests Completed!\n";
std::cout << "===========================================\n";
}
@@ -1,27 +1,73 @@
#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"
extern Zone *zone;
using json = nlohmann::json;
void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
struct HandinEntry {
std::string item_id = "0";
uint32 count = 0;
const EQ::ItemInstance *item = nullptr;
bool is_multiquest_item = false; // state
};
struct HandinMoney {
uint32 platinum = 0;
uint32 gold = 0;
uint32 silver = 0;
uint32 copper = 0;
};
struct Handin {
std::vector<HandinEntry> items = {}; // items can be removed from this set as successful handins are made
HandinMoney money = {}; // money can be removed from this set as successful handins are made
};
struct TestCase {
std::string description;
Handin hand_in;
Handin required;
Handin returned;
bool handin_check_result;
};
void RunSerializedTest(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);
}
}
std::string SerializeHandin(const std::map<std::string, uint32> &items, const HandinMoney &money)
{
json j;
j["items"] = items;
j["money"] = {
{"platinum", money.platinum},
{"gold", money.gold},
{"silver", money.silver},
{"copper", money.copper}
};
return j.dump();
}
void ZoneCLI::TestNpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
{
if (cmd[{"-h", "--help"}]) {
return;
}
uint32 break_length = 50;
int failed_count = 0;
RegisterExecutablePlatform(EQEmuExePlatform::ExePlatformZoneSidecar);
LogInfo("{}", Strings::Repeat("-", break_length));
LogInfo("Booting test zone for NPC handins");
LogInfo("{}", Strings::Repeat("-", break_length));
LogSys.SilenceConsoleLogging();
Zone::Bootup(ZoneID("qrg"), 0, false);
@@ -30,9 +76,9 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
entity_list.Process();
entity_list.MobProcess();
LogInfo("{}", Strings::Repeat("-", break_length));
LogInfo("> Done booting test zone");
LogInfo("{}", Strings::Repeat("-", break_length));
std::cout << "===========================================\n";
std::cout << "\uFE0F> Running Hand-in Tests...\n";
std::cout << "===========================================\n\n";
Client *c = new Client();
auto npc_type = content_db.LoadNPCTypesData(754008);
@@ -46,36 +92,6 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
entity_list.AddNPC(npc);
LogInfo("> Spawned NPC [{}]", npc->GetCleanName());
LogInfo("> Spawned client [{}]", c->GetCleanName());
struct HandinEntry {
std::string item_id = "0";
uint32 count = 0;
const EQ::ItemInstance *item = nullptr;
bool is_multiquest_item = false; // state
};
struct HandinMoney {
uint32 platinum = 0;
uint32 gold = 0;
uint32 silver = 0;
uint32 copper = 0;
};
struct Handin {
std::vector<HandinEntry> items = {}; // items can be removed from this set as successful handins are made
HandinMoney money = {}; // money can be removed from this set as successful handins are made
};
struct TestCase {
std::string description = "";
Handin hand_in;
Handin required;
Handin returned;
bool handin_check_result;
};
std::vector<TestCase> test_cases = {
TestCase{
.description = "Test basic cloth-cap hand-in",
@@ -155,7 +171,10 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
.items = {},
.money = {.platinum = 100},
},
.returned = {},
.returned = {
.items = {},
.money = {.platinum = 1},
},
.handin_check_result = false,
},
TestCase{
@@ -168,7 +187,10 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
.items = {},
.money = {.platinum = 100, .gold = 100, .silver = 100, .copper = 100},
},
.returned = {},
.returned = {
.items = {},
.money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1},
},
.handin_check_result = false,
},
TestCase{
@@ -217,8 +239,11 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
},
.returned = {
.items = {
HandinEntry{.item_id = "1001", .count = 1},
HandinEntry{
.item_id = "1001", .count = 0,
},
},
.money = {.platinum = 1},
},
.handin_check_result = false,
},
@@ -304,12 +329,7 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
HandinEntry{.item_id = "1007", .count = 1},
HandinEntry{.item_id = "1007", .count = 1},
},
.money = {
.platinum = 1,
.gold = 666,
.silver = 234,
.copper = 444,
},
.money = {},
},
.handin_check_result = true,
},
@@ -402,8 +422,8 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
},
};
std::map<std::string, uint32> hand_ins;
std::map<std::string, uint32> required;
std::map<std::string, uint32> hand_ins;
std::map<std::string, uint32> required;
std::vector<EQ::ItemInstance *> items;
LogSys.EnableConsoleLogging();
@@ -411,14 +431,12 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
// turn this on to see debugging output
LogSys.log_settings[Logs::NpcHandin].log_to_console = std::getenv("DEBUG") ? 3 : 0;
LogInfo("{}", Strings::Repeat("-", break_length));
for (auto &test_case: test_cases) {
for (auto &test: test_cases) {
hand_ins.clear();
required.clear();
items.clear();
for (auto &hand_in: test_case.hand_in.items) {
for (auto &hand_in: test.hand_in.items) {
auto item_id = Strings::ToInt(hand_in.item_id);
EQ::ItemInstance *inst = database.CreateItem(item_id);
if (inst->IsStackable()) {
@@ -434,72 +452,76 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
}
// money
if (test_case.hand_in.money.platinum > 0) {
hand_ins["platinum"] = test_case.hand_in.money.platinum;
if (test.hand_in.money.platinum > 0) {
hand_ins["platinum"] = test.hand_in.money.platinum;
}
if (test_case.hand_in.money.gold > 0) {
hand_ins["gold"] = test_case.hand_in.money.gold;
if (test.hand_in.money.gold > 0) {
hand_ins["gold"] = test.hand_in.money.gold;
}
if (test_case.hand_in.money.silver > 0) {
hand_ins["silver"] = test_case.hand_in.money.silver;
if (test.hand_in.money.silver > 0) {
hand_ins["silver"] = test.hand_in.money.silver;
}
if (test_case.hand_in.money.copper > 0) {
hand_ins["copper"] = test_case.hand_in.money.copper;
if (test.hand_in.money.copper > 0) {
hand_ins["copper"] = test.hand_in.money.copper;
}
for (auto &req: test_case.required.items) {
for (auto &req: test.required.items) {
required[req.item_id] = req.count;
}
// money
if (test_case.required.money.platinum > 0) {
required["platinum"] = test_case.required.money.platinum;
if (test.required.money.platinum > 0) {
required["platinum"] = test.required.money.platinum;
}
if (test_case.required.money.gold > 0) {
required["gold"] = test_case.required.money.gold;
if (test.required.money.gold > 0) {
required["gold"] = test.required.money.gold;
}
if (test_case.required.money.silver > 0) {
required["silver"] = test_case.required.money.silver;
if (test.required.money.silver > 0) {
required["silver"] = test.required.money.silver;
}
if (test_case.required.money.copper > 0) {
required["copper"] = test_case.required.money.copper;
if (test.required.money.copper > 0) {
required["copper"] = test.required.money.copper;
}
auto result = npc->CheckHandin(c, hand_ins, required, items);
if (result != test_case.handin_check_result) {
failed_count++;
LogError("FAIL [{}]", test_case.description);
// print out the hand-ins
LogError("Hand-ins >");
for (auto &hand_in: hand_ins) {
LogError(" > Item [{}] count [{}]", hand_in.first, hand_in.second);
}
LogError("Required >");
for (auto &req: required) {
LogError(" > Item [{}] count [{}]", req.first, req.second);
}
LogError("Expected [{}] got [{}]", test_case.handin_check_result, result);
}
else {
LogInfo("PASS [{}]", test_case.description);
}
RunTest(test.description, test.handin_check_result, result);
auto returned = npc->ReturnHandinItems(c);
// assert that returned items are expected
for (auto &item: test_case.returned.items) {
auto found = false;
for (auto &ret: returned.items) {
if (ret.item_id == item.item_id) {
found = true;
break;
}
}
if (!found) {
LogError("Returned item [{}] not expected", item.item_id);
}
std::map<std::string, uint32> returned_items;
HandinMoney returned_money{};
// Serialize returned items
for (const auto &ret: returned.items) {
// if (ret.item->IsStackable() && ret.item->GetCharges() != ret.count) {
// ret.item->SetCharges(ret.count);
// }
returned_items[ret.item_id] += ret.count;
}
// Serialize returned money
returned_money.platinum = returned.money.platinum;
returned_money.gold = returned.money.gold;
returned_money.silver = returned.money.silver;
returned_money.copper = returned.money.copper;
// Serialize expected and actual return values for comparison
std::map<std::string, uint32> expected_returned_items;
for (const auto &entry: test.returned.items) {
expected_returned_items[entry.item_id] += entry.count;
}
std::string expected_serialized = SerializeHandin(
expected_returned_items,
test.returned.money
);
std::string actual_serialized = SerializeHandin(returned_items, returned_money);
// Run serialization check test
RunSerializedTest(test.description + " (Return Validation)", expected_serialized, actual_serialized);
npc->ResetHandin();
if (LogSys.log_settings[Logs::NpcHandin].log_to_console > 0) {
@@ -508,11 +530,7 @@ void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &
}
}
if (failed_count > 0) {
LogError("Failed [{}] tests", failed_count);
std::exit(1);
}
else {
LogInfo("All tests passed");
}
std::cout << "\n===========================================\n";
std::cout << "✅ All NPC Hand-in Tests Completed!\n";
std::cout << "===========================================\n";
}
@@ -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;
@@ -16,12 +16,6 @@ void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std
uint32 break_length = 50;
int failed_count = 0;
RegisterExecutablePlatform(EQEmuExePlatform::ExePlatformZoneSidecar);
LogInfo("{}", Strings::Repeat("-", break_length));
LogInfo("Booting test zone for NPC handins (MultiQuest)");
LogInfo("{}", Strings::Repeat("-", break_length));
LogSys.SilenceConsoleLogging();
Zone::Bootup(ZoneID("qrg"), 0, false);
@@ -30,9 +24,9 @@ void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std
entity_list.Process();
entity_list.MobProcess();
LogInfo("{}", Strings::Repeat("-", break_length));
LogInfo("> Done booting test zone");
LogInfo("{}", Strings::Repeat("-", break_length));
std::cout << "===========================================\n";
std::cout << "\uFE0F> Running Hand-in Tests (Multi-Quest)...\n";
std::cout << "===========================================\n\n";
Client *c = new Client();
auto npc_type = content_db.LoadNPCTypesData(754008);
@@ -47,9 +41,6 @@ void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std
entity_list.AddNPC(npc);
npc->MultiQuestEnable();
LogInfo("> Spawned NPC [{}]", npc->GetCleanName());
LogInfo("> Spawned client [{}]", c->GetCleanName());
struct HandinEntry {
std::string item_id = "0";
uint32 count = 0;
@@ -108,12 +99,10 @@ void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std
// turn this on to see debugging output
LogSys.log_settings[Logs::NpcHandin].log_to_console = std::getenv("DEBUG") ? 3 : 0;
LogInfo("{}", Strings::Repeat("-", break_length));
for (auto &test_case: test_cases) {
for (auto &test: test_cases) {
required.clear();
for (auto &hand_in: test_case.hand_in.items) {
for (auto &hand_in: test.hand_in.items) {
hand_ins.clear();
items.clear();
@@ -135,72 +124,43 @@ void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std
}
// money
if (test_case.hand_in.money.platinum > 0) {
hand_ins["platinum"] = test_case.hand_in.money.platinum;
if (test.hand_in.money.platinum > 0) {
hand_ins["platinum"] = test.hand_in.money.platinum;
}
if (test_case.hand_in.money.gold > 0) {
hand_ins["gold"] = test_case.hand_in.money.gold;
if (test.hand_in.money.gold > 0) {
hand_ins["gold"] = test.hand_in.money.gold;
}
if (test_case.hand_in.money.silver > 0) {
hand_ins["silver"] = test_case.hand_in.money.silver;
if (test.hand_in.money.silver > 0) {
hand_ins["silver"] = test.hand_in.money.silver;
}
if (test_case.hand_in.money.copper > 0) {
hand_ins["copper"] = test_case.hand_in.money.copper;
if (test.hand_in.money.copper > 0) {
hand_ins["copper"] = test.hand_in.money.copper;
}
for (auto &req: test_case.required.items) {
for (auto &req: test.required.items) {
required[req.item_id] = req.count;
}
// money
if (test_case.required.money.platinum > 0) {
required["platinum"] = test_case.required.money.platinum;
if (test.required.money.platinum > 0) {
required["platinum"] = test.required.money.platinum;
}
if (test_case.required.money.gold > 0) {
required["gold"] = test_case.required.money.gold;
if (test.required.money.gold > 0) {
required["gold"] = test.required.money.gold;
}
if (test_case.required.money.silver > 0) {
required["silver"] = test_case.required.money.silver;
if (test.required.money.silver > 0) {
required["silver"] = test.required.money.silver;
}
if (test_case.required.money.copper > 0) {
required["copper"] = test_case.required.money.copper;
if (test.required.money.copper > 0) {
required["copper"] = test.required.money.copper;
}
auto result = npc->CheckHandin(c, hand_ins, required, items);
if (result != test_case.handin_check_result) {
failed_count++;
LogError("FAIL [{}]", test_case.description);
// print out the hand-ins
LogError("Hand-ins >");
for (auto &item: npc->GetHandin().items) {
LogError(" > Item [{}] count [{}]", item.item_id, item.count);
}
LogError("Required >");
for (auto &req: required) {
LogError(" > Item [{}] count [{}]", req.first, req.second);
}
LogError("Expected [{}] got [{}]", test_case.handin_check_result, result);
}
else {
LogInfo("PASS [{}]", test_case.description);
}
RunTest(test.description, test.handin_check_result, result);
auto returned = npc->ReturnHandinItems(c);
// assert that returned items are expected
for (auto &item: test_case.returned.items) {
auto found = false;
for (auto &ret: returned.items) {
if (ret.item_id == item.item_id) {
found = true;
break;
}
}
if (!found) {
LogError("Returned item [{}] not expected", item.item_id);
}
}
npc->ResetHandin();
if (LogSys.log_settings[Logs::NpcHandin].log_to_console > 0) {
@@ -209,11 +169,7 @@ void ZoneCLI::NpcHandinsMultiQuest(int argc, char **argv, argh::parser &cmd, std
}
}
if (failed_count > 0) {
LogError("Failed [{}] tests", failed_count);
std::exit(1);
}
else {
LogInfo("All tests passed");
}
std::cout << "\n===========================================\n";
std::cout << "✅ All NPC Hand-in Tests Completed (Multi-Quest)!\n";
std::cout << "===========================================\n";
}
File diff suppressed because it is too large Load Diff
+92 -18
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();
@@ -2549,40 +2553,59 @@ void Client::ChangeLastName(std::string last_name) {
safe_delete(outapp);
}
bool Client::ChangeFirstName(const char* in_firstname, const char* gmname)
// Deprecated, this packet does not actually work in ROF2
bool Client::ChangeFirstName(const std::string in_firstname, const std::string gmname)
{
// check duplicate name
bool used_name = database.IsNameUsed((const char*) in_firstname);
if (used_name) {
if (!ChangeFirstName(in_firstname)) {
return false;
}
// update character_
if(!database.UpdateName(GetName(), in_firstname))
return false;
// update pp
memset(m_pp.name, 0, sizeof(m_pp.name));
snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname);
strcpy(name, m_pp.name);
Save();
// send name update packet
auto outapp = new EQApplicationPacket(OP_GMNameChange, sizeof(GMName_Struct));
GMName_Struct* gmn=(GMName_Struct*)outapp->pBuffer;
strn0cpy(gmn->gmname,gmname,64);
strn0cpy(gmn->gmname,gmname.c_str(),64);
strn0cpy(gmn->oldname,GetName(),64);
strn0cpy(gmn->newname,in_firstname,64);
strn0cpy(gmn->newname,in_firstname.c_str(),64);
gmn->unknown[0] = 1;
gmn->unknown[1] = 1;
gmn->unknown[2] = 1;
entity_list.QueueClients(this, outapp, false);
safe_delete(outapp);
// success
return true;
}
bool Client::ChangeFirstName(const std::string in_firstname)
{
// check duplicate name
bool used_name = database.IsNameUsed(in_firstname) || database.IsPetNameUsed(in_firstname);
if (used_name || !database.CheckNameFilter(in_firstname, false)) {
return false;
}
// update character_
if(!database.UpdateNameByID(CharacterID(), in_firstname))
return false;
// Send Name Update to Clients
SendRename(this, GetName(), in_firstname.c_str());
SetName(in_firstname.c_str());
// update pp
memset(m_pp.name, 0, sizeof(m_pp.name));
snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname.c_str());
strcpy(name, m_pp.name);
Save();
// Update the active char in account table
database.UpdateLiveChar(in_firstname, AccountID());
// finally, update the /who list
UpdateWho();
// success
ClearNameChange();
return true;
}
@@ -4737,6 +4760,57 @@ bool Client::KeyRingRemove(uint32 item_id)
);
}
bool Client::IsNameChangeAllowed() {
if (RuleB(Character, AlwaysAllowNameChange)) {
return true;
}
auto k = GetScopedBucketKeys();
k.key = "name_change_allowed";
auto b = DataBucket::GetData(k);
if (!b.value.empty()) {
return true;
}
return false;
}
bool Client::ClearNameChange() {
if (!IsNameChangeAllowed()) {
return false;
}
auto k = GetScopedBucketKeys();
k.key = "name_change_allowed";
DataBucket::DeleteData(k);
return true;
}
void Client::InvokeChangeNameWindow(bool immediate) {
if (!IsNameChangeAllowed()) {
return;
}
auto packet_op = immediate ? OP_InvokeNameChangeImmediate : OP_InvokeNameChangeLazy;
auto outapp = new EQApplicationPacket(packet_op, 0);
QueuePacket(outapp);
safe_delete(outapp);
}
void Client::GrantNameChange() {
auto k = GetScopedBucketKeys();
k.key = "name_change_allowed";
k.value = "allowed"; // potentially put a timestamp here
DataBucket::SetData(k);
InvokeChangeNameWindow(true);
}
bool Client::IsPetNameChangeAllowed() {
if (RuleB(Pets, AlwaysAllowPetRename)) {
return true;
+10 -2
View File
@@ -332,6 +332,10 @@ public:
bool KeyRingClear();
bool KeyRingRemove(uint32 item_id);
void KeyRingList();
bool IsNameChangeAllowed();
void InvokeChangeNameWindow(bool immediate = true);
bool ClearNameChange();
void GrantNameChange();
bool IsPetNameChangeAllowed();
void GrantPetNameChange();
void ClearPetNameChange();
@@ -404,6 +408,7 @@ public:
void LoadParcels();
std::map<uint32, CharacterParcelsRepository::CharacterParcels> GetParcels() { return m_parcels; }
int32 FindNextFreeParcelSlot(uint32 char_id);
int32 FindNextFreeParcelSlotUsingMemory();
void SendParcelIconStatus();
void SendBecomeTraderToWorld(Client *trader, BazaarTraderBarterActions action);
@@ -442,6 +447,8 @@ public:
int64 ValidateBuyLineCost(std::map<uint32, BuylineItemDetails_Struct>& item_map);
bool DoBarterBuyerChecks(BuyerLineSellItem_Struct& sell_line);
bool DoBarterSellerChecks(BuyerLineSellItem_Struct& sell_line);
void CancelBuyerTradeWindow();
void CancelTraderTradeWindow();
void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho);
bool ShouldISpawnFor(Client *c) { return !GMHideMe(c) && !IsHoveringForRespawn(); }
@@ -508,7 +515,8 @@ public:
bool AutoAttackEnabled() const { return auto_attack; }
bool AutoFireEnabled() const { return auto_fire; }
bool ChangeFirstName(const char* in_firstname,const char* gmname);
bool ChangeFirstName(const std::string in_firstname,const std::string gmname);
bool ChangeFirstName(const std::string in_firstname);
void Duck();
void Stand();
@@ -1900,7 +1908,7 @@ public:
void SendEvolvingPacket(int8 action, const CharacterEvolvingItemsRepository::CharacterEvolvingItems &item);
void DoEvolveItemToggle(const EQApplicationPacket* app);
void DoEvolveItemDisplayFinalResult(const EQApplicationPacket* app);
bool DoEvolveCheckProgression(const EQ::ItemInstance &inst);
bool DoEvolveCheckProgression(EQ::ItemInstance &inst);
void SendEvolveXPWindowDetails(const EQApplicationPacket* app);
void DoEvolveTransferXP(const EQApplicationPacket* app);
void SendEvolveXPTransferWindow();
+34 -32
View File
@@ -120,48 +120,46 @@ int Client::GetBotSpawnLimit(uint8 class_id) {
}
const auto& zones_list = Strings::Split(RuleS(Bots, ZonesWithSpawnLimits), ",");
const auto& zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ",");
int i = 0;
for (const auto& result : zones_list) {
try {
if (
std::stoul(result) == zone->GetZoneID() &&
std::stoul(zones_limits_list[i]) < bot_spawn_limit
) {
bot_spawn_limit = std::stoul(zones_limits_list[i]);
if (!zones_list.empty()) {
auto it = std::find(zones_list.begin(), zones_list.end(), std::to_string(zone->GetZoneID()));
break;
if (it != zones_list.end()) {
const auto& zones_limits_list = Strings::Split(RuleS(Bots, ZoneSpawnLimits), ",");
if (zones_list.size() == zones_limits_list.size()) {
try {
auto new_limit = std::stoul(zones_limits_list[std::distance(zones_list.begin(), it)]);
if (new_limit < bot_spawn_limit) {
bot_spawn_limit = new_limit;
}
} catch (const std::exception& e) {
LogInfo("Invalid entry in Rule Bots:ZoneSpawnLimits: [{}]", e.what());
}
}
++i;
}
catch (const std::exception& e) {
LogInfo("Invalid entry in Rule VegasScaling:SpecialScalingZones or SpecialScalingZonesVersions: [{}]", e.what());
}
}
const auto& zones_forced_list = Strings::Split(RuleS(Bots, ZonesWithForcedSpawnLimits), ",");
const auto& zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ",");
i = 0;
for (const auto& result : zones_forced_list) {
try {
if (
std::stoul(result) == zone->GetZoneID() &&
std::stoul(zones_forced_limits_list[i]) != bot_spawn_limit
) {
bot_spawn_limit = std::stoul(zones_forced_limits_list[i]);
if (!zones_forced_list.empty()) {
auto it = std::find(zones_forced_list.begin(), zones_forced_list.end(), std::to_string(zone->GetZoneID()));
break;
if (it != zones_forced_list.end()) {
const auto& zones_forced_limits_list = Strings::Split(RuleS(Bots, ZoneForcedSpawnLimits), ",");
if (zones_forced_list.size() == zones_forced_limits_list.size()) {
try {
auto new_limit = std::stoul(zones_forced_limits_list[std::distance(zones_forced_list.begin(), it)]);
if (new_limit != bot_spawn_limit) {
bot_spawn_limit = new_limit;
}
} catch (const std::exception& e) {
LogInfo("Invalid entry in Rule Bots:ZoneForcedSpawnLimits: [{}]", e.what());
}
}
++i;
}
catch (const std::exception& e) {
LogInfo("Invalid entry in Rule VegasScaling:SpecialScalingZones or SpecialScalingZonesVersions: [{}]", e.what());
}
}
@@ -258,6 +256,8 @@ int Client::GetDefaultBotSettings(uint8 setting_type, uint16 bot_setting) {
return GetDefaultSpellTypeMinThreshold(bot_setting);
case BotSettingCategories::SpellMaxThreshold:
return GetDefaultSpellTypeMaxThreshold(bot_setting);
default:
return 0; // default return for any unsupported setting type
}
}
@@ -271,6 +271,8 @@ int Client::GetBotSetting(uint8 setting_type, uint16 bot_setting) {
return GetSpellTypeMinThreshold(bot_setting);
case BotSettingCategories::SpellMaxThreshold:
return GetSpellTypeMaxThreshold(bot_setting);
default:
return 0; // default return for any unsupported setting type
}
}
+45 -2
View File
@@ -71,7 +71,7 @@ void Client::SendEvolvingPacket(const int8 action, const CharacterEvolvingItemsR
void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
{
std::vector<const EQ::ItemInstance *> queue{};
std::vector<EQ::ItemInstance *> queue{};
for (auto &[key, inst]: GetInv().GetWorn()) {
LogEvolveItemDetail(
@@ -128,6 +128,7 @@ void Client::ProcessEvolvingItem(const uint64 exp, const Mob *mob)
evolve_amount = exp * RuleR(EvolvingItems, PercentOfSoloExperience) / 100;
}
inst->SetEvolveAddToCurrentAmount(evolve_amount);
inst->CalculateEvolveProgression();
auto e = CharacterEvolvingItemsRepository::SetCurrentAmountAndProgression(
@@ -298,7 +299,7 @@ void Client::DoEvolveItemDisplayFinalResult(const EQApplicationPacket *app)
}
}
bool Client::DoEvolveCheckProgression(const EQ::ItemInstance &inst)
bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst)
{
if (inst.GetEvolveProgression() < 100 || inst.GetEvolveLvl() == inst.GetMaxEvolveLvl()) {
return false;
@@ -315,6 +316,48 @@ bool Client::DoEvolveCheckProgression(const EQ::ItemInstance &inst)
return false;
}
if (RuleB(EvolvingItems, EnableParcelMerchants) &&
!RuleB(EvolvingItems, DestroyAugmentsOnEvolve) &&
inst.IsAugmented()
) {
auto const augs = inst.GetAugmentIDs();
std::vector<CharacterParcelsRepository::CharacterParcels> parcels;
for (auto const &item_id: augs) {
if (!item_id) {
continue;
}
CharacterParcelsRepository::CharacterParcels p{};
p.char_id = CharacterID();
p.from_name = "Evolving Item Sub-System";
p.note = fmt::format(
"System automatically removed from {} which recently evolved.",
inst.GetItem()->Name
);
p.slot_id = FindNextFreeParcelSlotUsingMemory();
p.sent_date = time(nullptr);
p.item_id = item_id;
p.quantity = 1;
if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
PlayerEvent::ParcelSend e{};
e.from_player_name = p.from_name;
e.to_player_name = GetCleanName();
e.item_id = p.item_id;
e.quantity = 1;
e.sent_date = p.sent_date;
RecordPlayerEventLog(PlayerEvent::PARCEL_SEND, e);
}
parcels.push_back(p);
}
CharacterParcelsRepository::InsertMany(database, parcels);
SendParcelStatus();
SendParcelIconStatus();
}
CheckItemDiscoverability(new_inst->GetID());
PlayerEvent::EvolveItem e{};
+50 -22
View File
@@ -73,6 +73,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "../common/repositories/character_stats_record_repository.h"
#include "dialogue_window.h"
#include "../common/rulesys.h"
#include "../common/repositories/adventure_members_repository.h"
extern QueryServ* QServ;
extern Zone* zone;
@@ -826,6 +827,10 @@ void Client::CompleteConnect()
if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) {
InvokeChangePetName(false);
}
if (IsNameChangeAllowed() && !RuleB(Character, AlwaysAllowNameChange)) {
InvokeChangeNameWindow(false);
}
}
if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) {
@@ -909,11 +914,14 @@ void Client::CompleteConnect()
SendDynamicZoneUpdates();
/** Request adventure info **/
auto pack = new ServerPacket(ServerOP_AdventureDataRequest, 64);
strcpy((char*)pack->pBuffer, GetName());
worldserver.SendPacket(pack);
delete pack;
// Request adventure info
auto members = AdventureMembersRepository::GetWhere(database, fmt::format("charid = {}", CharacterID()));
if (!members.empty()) {
auto pack = new ServerPacket(ServerOP_AdventureDataRequest, 64);
strcpy((char*)pack->pBuffer, GetName());
worldserver.SendPacket(pack);
delete pack;
}
if (IsClient() && CastToClient()->ClientVersionBit() & EQ::versions::maskUFAndLater) {
EQApplicationPacket *outapp = MakeBuffsPacket(false);
@@ -4256,7 +4264,7 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app)
else {
OnDisconnect(true);
}
return;
}
@@ -4548,14 +4556,14 @@ void Client::Handle_OP_ChangePetName(const EQApplicationPacket *app) {
auto p = (ChangePetName_Struct *) app->pBuffer;
if (!IsPetNameChangeAllowed()) {
p->response_code = ChangePetNameResponse::NotEligible;
p->response_code = ChangeNameResponse::Ineligible;
QueuePacket(app);
return;
}
p->response_code = ChangePetNameResponse::Denied;
p->response_code = ChangeNameResponse::Denied;
if (ChangePetName(p->new_pet_name)) {
p->response_code = ChangePetNameResponse::Accepted;
p->response_code = ChangeNameResponse::Accepted;
}
QueuePacket(app);
@@ -6776,6 +6784,21 @@ void Client::Handle_OP_GMLastName(const EQApplicationPacket *app)
void Client::Handle_OP_GMNameChange(const EQApplicationPacket *app)
{
if (app->size == sizeof(AltChangeName_Struct)) {
auto p = (AltChangeName_Struct *) app->pBuffer;
if (!IsNameChangeAllowed()) {
p->response_code = ChangeNameResponse::Ineligible;
QueuePacket(app);
return;
}
p->response_code = ChangeFirstName(p->new_name) ? ChangeNameResponse::Accepted : ChangeNameResponse::Denied;
QueuePacket(app);
return;
}
if (app->size != sizeof(GMName_Struct)) {
LogError("Wrong size: OP_GMNameChange, size=[{}], expected [{}]", app->size, sizeof(GMName_Struct));
return;
@@ -7653,7 +7676,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
log.char_id = CharacterID();
log.guild_id = GuildID();
log.item_id = inst->GetID();
log.quantity = inst->GetCharges();
log.quantity = 1;
if (inst->GetCharges() > 0 || inst->IsStackable() || inst->GetItem()->MaxCharges > 0) {
log.quantity = inst->GetCharges();
}
if (inst->IsAugmented()) {
auto augs = inst->GetAugmentIDs();
log.aug_slot_one = augs.at(0);
@@ -7737,7 +7764,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
item.guild_id = GuildID();
item.area = GuildBankDepositArea;
item.item_id = cursor_item->ID;
item.quantity = cursor_item_inst->GetCharges();
item.quantity = 1;
if (cursor_item_inst->GetCharges() > 0 || cursor_item_inst->IsStackable() || cursor_item->MaxCharges > 0) {
item.quantity = cursor_item_inst->GetCharges();
}
item.donator = GetCleanName();
item.permissions = GuildBankBankerOnly;
if (cursor_item_inst->IsAugmented()) {
@@ -7821,14 +7852,11 @@ void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
break;
}
if (inst->GetCharges() > 0) {
gbwis->Quantity = 1;
if (inst->GetCharges() > 0 || inst->IsStackable() || inst->GetItem()->MaxCharges > 0) {
gbwis->Quantity = inst->GetCharges();
}
if (inst->GetCharges() < 0) {
gbwis->Quantity = 1;
}
PushItemOnCursor(*inst.get());
SendItemPacket(EQ::invslot::slotCursor, inst.get(), ItemPacketLimbo);
GuildBanks->DeleteItem(GuildID(), gbwis->Area, gbwis->SlotID, gbwis->Quantity, this);
@@ -7956,7 +7984,7 @@ void Client::Handle_OP_GuildCreate(const EQApplicationPacket *app)
}
SetGuildID(new_guild_id);
SendGuildList();
UpdateWho();
guild_mgr.MemberAdd(new_guild_id, CharacterID(), GetLevel(), GetClass(), GUILD_LEADER, GetZoneID(), GetName());
guild_mgr.SendGuildRefresh(new_guild_id, true, true, true, true);
guild_mgr.SendToWorldSendGuildList();
@@ -8125,7 +8153,7 @@ void Client::Handle_OP_GuildInvite(const EQApplicationPacket *app)
if (!invitee) {
Message(
Chat::Red,
"Prospective guild member %s must be in zone to preform guild operations on them.",
"Prospective guild member %s must be in zone to perform guild operations on them.",
gc->othername
);
return;
@@ -11038,7 +11066,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
if (!target)
break;
if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(target) || !CheckLosCheat(target))) {
if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(target)) {
mypet->SayString(this, NOT_LEGAL_TARGET);
break;
}
@@ -11106,7 +11134,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
break;
}
if (RuleB(Map, CheckForLoSCheat) && (!DoLosChecks(GetTarget()) || !CheckLosCheat(GetTarget()))) {
if (RuleB(Pets, PetsRequireLoS) && !DoLosChecks(GetTarget())) {
mypet->SayString(this, NOT_LEGAL_TARGET);
break;
}
@@ -15458,7 +15486,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app)
);
Message(
Chat::Yellow,
"Direct inventory delivey is not yet implemented. Please visit the vendor directly or purchase via parcel delivery."
"Direct inventory delivery is not yet implemented. Please visit the vendor directly or purchase via parcel delivery."
);
in->method = BazaarByDirectToInventory;
in->sub_action = Failed;
@@ -16974,7 +17002,7 @@ void Client::Handle_OP_GuildTributeDonateItem(const EQApplicationPacket *app)
SendGuildTributeDonateItemReply(in, favor);
if(player_event_logs.IsEventEnabled(PlayerEvent::GUILD_TRIBUTE_DONATE_ITEM)) {
if(inst && player_event_logs.IsEventEnabled(PlayerEvent::GUILD_TRIBUTE_DONATE_ITEM)) {
auto e = PlayerEvent::GuildTributeDonateItem{ .item_id = inst->GetID(),
.augment_1_id = inst->GetAugmentItemID(0),
.augment_2_id = inst->GetAugmentItemID(1),
+1 -1
View File
@@ -245,7 +245,7 @@ int command_init(void)
command_add("zonebootup", "[ZoneServerID] [shortname] - Make a zone server boot a specific zone", AccountStatus::GMLeadAdmin, command_zonebootup) ||
command_add("zoneinstance", "[Instance ID] [X] [Y] [Z] - Teleport to specified Instance by ID (coordinates are optional)", AccountStatus::Guide, command_zone_instance) ||
command_add("zoneshard", "[zone] [instance_id] - Teleport explicitly to a zone shard", AccountStatus::Player, command_zone_shard) ||
command_add("zoneshutdown", "[shortname] - Shut down a zone server", AccountStatus::GMLeadAdmin, command_zoneshutdown) ||
command_add("zoneshutdown", "[instance|zone] [Instance ID|Zone ID|Zone Short Name] - Shut down a zone server by Instance ID, Zone ID, or Zone Short Name", AccountStatus::GMLeadAdmin, command_zoneshutdown) ||
command_add("zsave", " Saves zheader to the database", AccountStatus::QuestTroupe, command_zsave)
) {
command_deinit();
-5
View File
@@ -111,7 +111,6 @@ void command_ipban(Client *c, const Seperator *sep);
void command_kick(Client *c, const Seperator *sep);
void command_killallnpcs(Client *c, const Seperator *sep);
void command_kill(Client *c, const Seperator *sep);
void command_level(Client *c, const Seperator *sep);
void command_list(Client *c, const Seperator *sep);
void command_lootsim(Client *c, const Seperator *sep);
void command_load_shared_memory(Client *c, const Seperator *sep);
@@ -139,7 +138,6 @@ void command_nudge(Client *c, const Seperator *sep);
void command_nukebuffs(Client *c, const Seperator *sep);
void command_nukeitem(Client *c, const Seperator *sep);
void command_object(Client *c, const Seperator *sep);
void command_oocmute(Client *c, const Seperator *sep);
void command_parcels(Client *c, const Seperator *sep);
void command_path(Client *c, const Seperator *sep);
void command_peqzone(Client *c, const Seperator *sep);
@@ -147,7 +145,6 @@ void command_petitems(Client *c, const Seperator *sep);
void command_picklock(Client *c, const Seperator *sep);
void command_profanity(Client *c, const Seperator *sep);
void command_push(Client *c, const Seperator *sep);
void command_pvp(Client *c, const Seperator *sep);
void command_raidloot(Client* c, const Seperator* sep);
void command_randomfeatures(Client *c, const Seperator *sep);
void command_refreshgroup(Client *c, const Seperator *sep);
@@ -201,8 +198,6 @@ void command_zone_instance(Client *c, const Seperator *sep);
void command_zone_shard(Client *c, const Seperator *sep);
void command_zonebootup(Client *c, const Seperator *sep);
void command_zoneshutdown(Client *c, const Seperator *sep);
void command_zopp(Client *c, const Seperator *sep);
void command_zsafecoords(Client *c, const Seperator *sep);
void command_zsave(Client *c, const Seperator *sep);
#include "bot.h"
+3 -3
View File
@@ -290,7 +290,7 @@ Corpse::Corpse(Client *c, int32 rez_exp, KilledByTypes in_killed_by) : Mob(
m_corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS));
m_loot_cooldown_timer.SetTimer(10);
m_check_rezzable_timer.SetTimer(1000);
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineTime));
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineCheckTime) * 1000);
m_corpse_rezzable_timer.Disable();
SetRezTimer(true);
@@ -583,7 +583,7 @@ Corpse::Corpse(
m_corpse_delay_timer.SetTimer(RuleI(NPC, CorpseUnlockTimer));
m_corpse_graveyard_timer.SetTimer(RuleI(Zone, GraveyardTimeMS));
m_loot_cooldown_timer.SetTimer(10);
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineTime));
m_check_owner_online_timer.SetTimer(RuleI(Character, CorpseOwnerOnlineCheckTime) * 1000);
m_check_rezzable_timer.SetTimer(1000);
m_corpse_rezzable_timer.Disable();
@@ -1569,7 +1569,7 @@ void Corpse::LootCorpseItem(Client *c, const EQApplicationPacket *app)
}
}
if (player_event_logs.IsEventEnabled(PlayerEvent::LOOT_ITEM) && !IsPlayerCorpse()) {
if (inst && player_event_logs.IsEventEnabled(PlayerEvent::LOOT_ITEM) && !IsPlayerCorpse()) {
auto e = PlayerEvent::LootItemEvent{
.item_id = inst->GetItem()->ID,
.item_name = inst->GetItem()->Name,
+145 -41
View File
@@ -27,7 +27,8 @@ void DataBucket::SetData(const std::string &bucket_key, const std::string &bucke
void DataBucket::SetData(const DataBucketKey &k_)
{
DataBucketKey k = k_; // copy the key so we can modify it
if (k.key.find(NESTED_KEY_DELIMITER) != std::string::npos) {
bool is_nested = k.key.find(NESTED_KEY_DELIMITER) != std::string::npos;
if (is_nested) {
k.key = Strings::Split(k.key, NESTED_KEY_DELIMITER).front();
}
@@ -63,6 +64,10 @@ void DataBucket::SetData(const DataBucketKey &k_)
if (isalpha(k.expires[0]) || isalpha(k.expires[k.expires.length() - 1])) {
expires_time_unix = static_cast<int64>(std::time(nullptr)) + Strings::TimeToSeconds(k.expires);
}
if (is_nested) {
LogDataBuckets("Nested keys can't expire; set expiration on the parent key");
expires_time_unix = 0;
}
}
b.expires = expires_time_unix;
@@ -75,26 +80,45 @@ void DataBucket::SetData(const DataBucketKey &k_)
std::string existing_value = r.id > 0 ? r.value : "{}";
json json_value = json::object();
try {
json_value = json::parse(existing_value);
} catch (json::parse_error &e) {
LogError("Failed to parse JSON for key [{}]: {}", k_.key, e.what());
json_value = json::object(); // Reset to an empty object on error
// Check if the JSON is valid
if (Strings::IsValidJson(existing_value)) {
try {
json_value = json::parse(existing_value);
} catch (json::parse_error &e) {
LogDataBuckets("Failed to parse JSON for key [{}] [{}]", k_.key, e.what());
json_value = json::object(); // Reset to an empty object on error
}
}
// Recursively merge new key-value pair into the JSON object
auto nested_keys = Strings::Split(k_.key, NESTED_KEY_DELIMITER);
auto top_key = nested_keys.front();
// remove the top-level key
nested_keys.erase(nested_keys.begin());
json *current = &json_value;
for (size_t i = 0; i < nested_keys.size(); ++i) {
const std::string &key_part = nested_keys[i];
if (i == nested_keys.size() - 1) {
LogDataBucketsDetail("Setting key [{}] key_part [{}]", k.key, key_part);
// If the key already exists and is an object or array, prevent overwriting to avoid data loss
if (current->contains(key_part) &&
((*current)[key_part].is_object() || (*current)[key_part].is_array())) {
LogDataBuckets("Attempted to overwrite an existing object or array at key [{}] - skipping", k_.key);
return;
}
// Set the value at the final key
(*current)[key_part] = k_.value;
} else {
// Traverse or create nested objects
if (!current->contains(key_part)) {
(*current)[key_part] = json::object();
LogDataBucketsDetail("Creating nested root key [{}] key_part [{}]", k.key, key_part);
} else if (!(*current)[key_part].is_object()) {
// If key exists but is not an object, reset to object to avoid conflicts
(*current)[key_part] = json::object();
@@ -105,7 +129,7 @@ void DataBucket::SetData(const DataBucketKey &k_)
// Serialize JSON back to string
b.value = json_value.dump();
b.key_ = nested_keys.front(); // Use the top-level key
b.key_ = top_key; // Use the top-level key
}
if (bucket_id) {
@@ -142,12 +166,20 @@ DataBucketsRepository::DataBuckets DataBucket::ExtractNestedValue(
const std::string &full_key)
{
auto nested_keys = Strings::Split(full_key, NESTED_KEY_DELIMITER);
auto top_key = nested_keys.front();
nested_keys.erase(nested_keys.begin());
json json_value;
// Check if the JSON is valid
if (!Strings::IsValidJson(bucket.value)) {
LogDataBuckets("Invalid JSON for key [{}]", bucket.key_);
return DataBucketsRepository::NewEntity();
}
try {
json_value = json::parse(bucket.value); // Parse the JSON
} catch (json::parse_error &ex) {
LogError("Failed to parse JSON for key [{}]: {}", bucket.key_, ex.what());
LogDataBuckets("Failed to parse JSON for key [{}] [{}]", bucket.key_, ex.what());
return DataBucketsRepository::NewEntity(); // Return empty entity on parse error
}
@@ -336,44 +368,116 @@ bool DataBucket::GetDataBuckets(Mob *mob)
bool DataBucket::DeleteData(const DataBucketKey &k)
{
if (CanCache(k)) {
size_t size_before = g_data_bucket_cache.size();
bool is_nested_key = k.key.find(NESTED_KEY_DELIMITER) != std::string::npos;
// delete from cache where contents match
g_data_bucket_cache.erase(
std::remove_if(
g_data_bucket_cache.begin(),
g_data_bucket_cache.end(),
[&](DataBucketsRepository::DataBuckets &e) {
return CheckBucketMatch(e, k);
}
),
g_data_bucket_cache.end()
);
if (!is_nested_key) {
// Update cache
if (CanCache(k)) {
// delete from cache where contents match
g_data_bucket_cache.erase(
std::remove_if(
g_data_bucket_cache.begin(),
g_data_bucket_cache.end(),
[&](DataBucketsRepository::DataBuckets &e) {
return CheckBucketMatch(e, k);
}
),
g_data_bucket_cache.end()
);
}
LogDataBuckets(
"Deleting bucket key [{}] bot_id [{}] account_id [{}] character_id [{}] npc_id [{}] bot_id [{}] zone_id [{}] instance_id [{}] cache size before [{}] after [{}]",
k.key,
k.bot_id,
k.account_id,
k.character_id,
k.npc_id,
k.bot_id,
k.zone_id,
k.instance_id,
size_before,
g_data_bucket_cache.size()
// Regular key deletion, no nesting involved
return DataBucketsRepository::DeleteWhere(
database,
fmt::format("{} `key` = '{}'", DataBucket::GetScopedDbFilters(k), k.key)
);
}
return DataBucketsRepository::DeleteWhere(
database,
fmt::format(
"{} `key` = '{}'",
DataBucket::GetScopedDbFilters(k),
k.key
)
);
// If it's a nested key, retrieve the top-level JSON object
auto top_level_key = Strings::Split(k.key, NESTED_KEY_DELIMITER).front();
DataBucketKey top_level_k = k;
top_level_k.key = top_level_key;
auto r = GetData(top_level_k);
if (r.id == 0 || r.value.empty() || !Strings::IsValidJson(r.value)) {
LogDataBuckets("Attempted to delete nested key [{}] but parent key [{}] does not exist or is invalid JSON", k.key, top_level_key);
return false;
}
json json_value;
try {
json_value = json::parse(r.value);
} catch (json::parse_error &ex) {
LogDataBuckets("Failed to parse JSON for key [{}] [{}]", top_level_key, ex.what());
return false;
}
// Recursively remove the nested key
auto nested_keys = Strings::Split(k.key, NESTED_KEY_DELIMITER);
auto top_key = nested_keys.front();
nested_keys.erase(nested_keys.begin());
json *current = &json_value;
for (size_t i = 0; i < nested_keys.size(); ++i) {
const std::string &key_part = nested_keys[i];
if (i == nested_keys.size() - 1) {
// Last key in the hierarchy - delete it
if (current->contains(key_part)) {
current->erase(key_part);
LogDataBuckets("Deleted nested key [{}] from [{}]", key_part, k.key);
} else {
LogDataBuckets("Key [{}] not found in JSON - nothing to delete", k.key);
return false;
}
} else {
if (!current->contains(key_part) || !(*current)[key_part].is_object()) {
LogDataBuckets("Parent key [{}] does not exist or is not an object", key_part);
return false;
}
current = &(*current)[key_part];
}
}
// If the JSON object is now empty, delete the top-level key
if (json_value.empty()) {
LogDataBuckets("Top-level key [{}] is now empty, deleting entire entry", top_level_key);
// delete cache
if (CanCache(k)) {
g_data_bucket_cache.erase(
std::remove_if(
g_data_bucket_cache.begin(),
g_data_bucket_cache.end(),
[&](DataBucketsRepository::DataBuckets &e) {
return CheckBucketMatch(e, top_level_k);
}
),
g_data_bucket_cache.end()
);
}
return DataBucketsRepository::DeleteWhere(
database,
fmt::format("{} `key` = '{}'", DataBucket::GetScopedDbFilters(k), top_level_key)
);
}
// Otherwise, update the existing JSON without the deleted key
r.value = json_value.dump();
DataBucketsRepository::UpdateOne(database, r);
// Update cache
if (CanCache(k)) {
for (auto &e : g_data_bucket_cache) {
if (CheckBucketMatch(e, top_level_k)) {
e.value = r.value;
break;
}
}
}
return true;
}
std::string DataBucket::GetDataExpires(const DataBucketKey &k)
+68
View File
@@ -35,6 +35,9 @@
#include <string.h>
#include <glm/ext/matrix_transform.hpp>
#include <numbers>
#define OPEN_DOOR 0x02
#define CLOSE_DOOR 0x03
#define OPEN_INVDOOR 0x03
@@ -970,3 +973,68 @@ bool Doors::GetIsDoorBlacklisted()
bool Doors::IsDoorBlacklisted() {
return m_is_blacklisted_to_open;
}
bool Doors::IsDoorBetween(glm::vec4 loc_a, glm::vec4 loc_c, uint16 door_size, float door_depth, bool draw_box) {
glm::vec4 door_loc = GetPosition();
float half_size = door_size * 0.5f;
float half_depth = door_depth * 0.5f;
float normalized_heading = std::fmod(door_loc.w, 512.0f);
float heading_radians = normalized_heading * (std::numbers::pi / 256.0f);
glm::mat4 door_rotation = glm::rotate(glm::mat4(1.0f), -heading_radians, glm::vec3(0.0f, 0.0f, 1.0f));
glm::vec3 box_corner_one = glm::vec3(door_size, -half_depth, 0.0f);
glm::vec3 box_corner_two = glm::vec3(-door_size, -half_depth, 0.0f);
glm::vec3 box_corner_three = glm::vec3(-door_size, half_depth, 0.0f);
glm::vec3 box_corner_four = glm::vec3(door_size, half_depth, 0.0f);
glm::vec3 door_center_offset = glm::vec3(-(door_size * 0.75f), half_depth * 0.5f, 0.0f);
glm::vec3 door_center = glm::vec3(door_loc) + glm::vec3(door_rotation * glm::vec4(door_center_offset, 1.0f));
glm::mat4 transform = glm::translate(glm::mat4(1.0f), door_center) * door_rotation;
box_corner_one = glm::vec3(transform * glm::vec4(box_corner_one, 1.0f));
box_corner_two = glm::vec3(transform * glm::vec4(box_corner_two, 1.0f));
box_corner_three = glm::vec3(transform * glm::vec4(box_corner_three, 1.0f));
box_corner_four = glm::vec3(transform * glm::vec4(box_corner_four, 1.0f));
if (draw_box) {
NPC::SpawnZonePointNodeNPC("loc_a", loc_a);
NPC::SpawnZonePointNodeNPC("door_anchor", door_loc);
NPC::SpawnZonePointNodeNPC("loc_c", loc_c);
NPC::SpawnZonePointNodeNPC("box_corner_one", glm::vec4(box_corner_one.x, box_corner_one.y, box_corner_one.z, 0));
NPC::SpawnZonePointNodeNPC("box_corner_two", glm::vec4(box_corner_two.x, box_corner_two.y, box_corner_two.z, 0));
NPC::SpawnZonePointNodeNPC("box_corner_three", glm::vec4(box_corner_three.x, box_corner_three.y, box_corner_three.z, 0));
NPC::SpawnZonePointNodeNPC("box_corner_four", glm::vec4(box_corner_four.x, box_corner_four.y, box_corner_four.z, 0));
NPC::SpawnZonePointNodeNPC("box_center", glm::vec4(door_center.x, door_center.y, door_center.z, 0));
}
// Check if LoS intersects box
auto intersects_box = [](const glm::vec3& a, const glm::vec3& b, const glm::vec3& p1, const glm::vec3& p2) {
glm::vec3 ab = b - a;
glm::vec3 p1p2 = p2 - p1;
glm::vec3 cross = glm::cross(ab, p1p2);
float cross_magnitude_squared = glm::dot(cross, cross);
if (cross_magnitude_squared < 1e-6f) {
return false; // Lines are parallel or coincident
}
float t = glm::dot(glm::cross(p1 - a, p1p2), cross) / cross_magnitude_squared;
float u = glm::dot(glm::cross(p1 - a, ab), cross) / cross_magnitude_squared;
return (t >= 0.0f && t <= 1.0f && u >= 0.0f && u <= 1.0f);
};
// Check intersection with each edge of the door bounding box
glm::vec3 loc_a_vec3(loc_a.x, loc_a.y, loc_a.z);
glm::vec3 loc_c_vec3(loc_c.x, loc_c.y, loc_c.z);
if (
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_one, box_corner_two) ||
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_two, box_corner_three) ||
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_three, box_corner_four) ||
intersects_box(loc_a_vec3, loc_c_vec3, box_corner_four, box_corner_one)
) {
return true;
}
return false;
}
+1
View File
@@ -76,6 +76,7 @@ public:
bool IsDestinationZoneSame() const;
bool IsDoorBlacklisted();
bool IsDoorBetween(glm::vec4 loc_a, glm::vec4 loc_c, uint16 door_size = 15, float door_depth = 5.0f, bool draw_box = false);
const char* GetDoorZone() const { return m_zone_name; }
+44 -1
View File
@@ -25,6 +25,7 @@
#include "worldserver.h"
#include "../common/repositories/character_expedition_lockouts_repository.h"
#include "../common/repositories/dynamic_zone_lockouts_repository.h"
#include <cereal/types/utility.hpp>
extern WorldServer worldserver;
@@ -162,14 +163,28 @@ void DynamicZone::CacheAllFromDatabase()
}
}
dz->UpdateMembers();
zone->dynamic_zone_cache.emplace(dz_id, std::move(dz));
}
if (!zone->dynamic_zone_cache.empty())
{
RequestMemberStatuses();
}
LogInfo("Loaded [{}] dynamic zone(s)", Strings::Commify(zone->dynamic_zone_cache.size()));
LogDynamicZones("Caching [{}] dynamic zone(s) took [{}s]", zone->dynamic_zone_cache.size(), bench.elapsed());
}
void DynamicZone::RequestMemberStatuses()
{
ServerPacket pack(ServerOP_DzGetBulkMemberStatuses, sizeof(ServerDzCerealData_Struct));
auto buf = reinterpret_cast<ServerDzCerealData_Struct*>(pack.pBuffer);
buf->zone_id = static_cast<uint16_t>(zone->GetZoneID());
buf->inst_id = static_cast<uint16_t>(zone->GetInstanceID());
worldserver.SendPacket(&pack);
}
template <typename T>
DynamicZone* FindDynamicZone(T pred)
{
@@ -849,6 +864,34 @@ void DynamicZone::HandleWorldMessage(ServerPacket* pack)
}
break;
}
case ServerOP_DzGetBulkMemberStatuses:
{
if (zone)
{
std::vector<std::pair<uint32_t, std::vector<DynamicZoneMember>>> dzs;
dzs.reserve(zone->dynamic_zone_cache.size());
auto buf = reinterpret_cast<ServerDzCerealData_Struct*>(pack->pBuffer);
EQ::Util::MemoryStreamReader ss(buf->cereal_data, buf->cereal_size);
{
cereal::BinaryInputArchive archive(ss);
archive(dzs);
}
for (const auto& [dz_id, members] : dzs)
{
if (auto dz = DynamicZone::FindDynamicZoneByID(dz_id))
{
for (const auto& member : members)
{
dz->SetInternalMemberStatus(member.id, member.status);
}
dz->m_has_member_statuses = true;
}
}
}
break;
}
case ServerOP_DzUpdateMemberStatus:
{
auto buf = reinterpret_cast<ServerDzMemberStatus_Struct*>(pack->pBuffer);
+2 -1
View File
@@ -92,13 +92,13 @@ public:
void SendMemberNameToZoneMembers(const std::string& char_name, bool remove);
void SendMemberStatusToZoneMembers(const DynamicZoneMember& member);
void SetLocked(bool lock, bool update_db = false, DzLockMsg lock_msg = DzLockMsg::None, uint32_t color = Chat::Yellow);
void UpdateMembers();
std::string GetLootEvent(uint32_t id, DzLootEvent::Type type) const;
void SetLootEvent(uint32_t id, const std::string& event, DzLootEvent::Type type);
private:
static void StartAllClientRemovalTimers();
static void RequestMemberStatuses();
uint16_t GetCurrentInstanceID() const override;
uint16_t GetCurrentZoneID() const override;
@@ -125,6 +125,7 @@ private:
void SendWorldPlayerInvite(const std::string& inviter, const std::string& swap_name, const std::string& add_name, bool pending = false);
void SetUpdatedDuration(uint32_t seconds);
void TryAddClient(Client* add_client, const std::string& inviter, const std::string& swap_name, Client* leader = nullptr);
void UpdateMembers();
std::unique_ptr<EQApplicationPacket> CreateExpireWarningPacket(uint32_t minutes_remaining);
std::unique_ptr<EQApplicationPacket> CreateInfoPacket(bool clear = false);
+21 -7
View File
@@ -2914,7 +2914,7 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob)
return;
}
if (scanning_mob->GetID() <= 0) {
if (scanning_mob->GetID() <= 0 || scanning_mob->IsZoneController()) {
return;
}
@@ -2933,7 +2933,7 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob)
for (auto &e : mob_list) {
auto mob = e.second;
if (mob && mob->GetID() <= 0) {
if (mob && (mob->GetID() <= 0 || mob->IsZoneController())) {
continue;
}
@@ -3143,20 +3143,23 @@ void EntityList::Depop(bool StartSpawnTimer)
{
for (auto it = npc_list.begin(); it != npc_list.end(); ++it) {
NPC *pnpc = it->second;
if (pnpc) {
Mob *own = pnpc->GetOwner();
//do not depop player's pets...
if (own && own->IsClient())
//do not depop player/bot pets...
if (own && own->IsOfClientBot()) {
continue;
}
if (pnpc->IsHorse())
if (pnpc->IsHorse()) {
continue;
}
if (pnpc->IsFindable())
if (pnpc->IsFindable()) {
UpdateFindableNPCState(pnpc, true);
}
pnpc->WipeHateList();
pnpc->Depop(StartSpawnTimer);
}
}
@@ -5988,3 +5991,14 @@ void EntityList::SendMerchantInventory(Mob* m, int32 slot_id, bool is_delete)
return;
}
void EntityList::RestoreCorpse(NPC *npc, uint32_t decay_time)
{
uint16 corpse_id = npc->GetID();
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
auto c = entity_list.GetCorpseByID(corpse_id);
if (c) {
c->UnLock();
c->SetDecayTimer(decay_time);
}
}
+1
View File
@@ -580,6 +580,7 @@ public:
void SendMerchantEnd(Mob* merchant);
void SendMerchantInventory(Mob* m, int32 slot_id = -1, bool is_delete = false);
void RestoreCorpse(NPC* npc, uint32_t decay_time);
protected:
friend class Zone;
+27
View File
@@ -35,6 +35,26 @@ void DoorManipulation::CommandHandler(Client *c, const Seperator *sep)
);
}
if (arg1 == "drawbox") {
Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId());
if (door) {
uint16 door_size = 15;
float door_depth = 5.0f;
if (sep->IsNumber(2) && atof(sep->arg[2]) > 0) {
door_size = atof(sep->arg[2]);
}
if (sep->IsNumber(3) && atof(sep->arg[3]) > 0) {
door_depth = atof(sep->arg[3]);
}
door->IsDoorBetween(c->GetPosition(), (c->GetTarget() ? c->GetTarget()->GetPosition() : c->GetPosition()), door_size, door_depth, true);
}
}
// edit menu
if (arg1 == "edit") {
Doors *door = entity_list.GetDoorsByID(c->GetDoorToolEntityId());
@@ -544,6 +564,13 @@ void DoorManipulation::CommandHandler(Client *c, const Seperator *sep)
c->Message(Chat::White, "#door setinvertstate [0|1] | Sets selected door invert state");
c->Message(Chat::White, "#door setincline <incline> | Sets selected door incline");
c->Message(Chat::White, "#door opentype <opentype> | Sets selected door opentype");
c->Message(
Chat::White,
fmt::format(
"{} <door_size> <door_depth> | Draws a box for the door, default size = 15, depth = 5 if none defined",
Saylink::Silent("#door drawbox")
).c_str()
);
c->Message(
Chat::White,
fmt::format(
+2 -2
View File
@@ -197,7 +197,7 @@ void command_parcels(Client *c, const Seperator *sep)
send_to_client.at(0).character_name.c_str()
);
if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
if (inst && player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
PlayerEvent::ParcelSend e{};
e.from_player_name = parcel_out.from_name;
e.to_player_name = send_to_client.at(0).character_name;
@@ -281,7 +281,7 @@ void command_parcels(Client *c, const Seperator *sep)
send_to_client.at(0).character_name.c_str()
);
if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
if (inst && player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
PlayerEvent::ParcelSend e{};
e.from_player_name = parcel_out.from_name;
e.to_player_name = send_to_client.at(0).character_name;
+2 -6
View File
@@ -14,7 +14,7 @@ void SetName(Client *c, const Seperator *sep)
std::string new_name = sep->arg[2];
std::string old_name = t->GetCleanName();
if (t->ChangeFirstName(new_name.c_str(), c->GetCleanName())) {
if (t->ChangeFirstName(new_name, c->GetCleanName())) {
c->Message(
Chat::White,
fmt::format(
@@ -24,17 +24,13 @@ void SetName(Client *c, const Seperator *sep)
).c_str()
);
c->Message(Chat::White, "Sending player to char select.");
t->Kick("Name was changed");
return;
}
c->Message(
Chat::White,
fmt::format(
"Unable to rename {}. Check that the new name '{}' isn't already taken.",
"Unable to rename {}. Check that the new name '{}' isn't already taken (Including Pet Names), or isn't invalid",
old_name,
new_name
).c_str()
+2
View File
@@ -49,6 +49,7 @@
#include "show/zone_loot.cpp"
#include "show/zone_points.cpp"
#include "show/zone_status.cpp"
#include "show/zone_variables.cpp"
void command_show(Client *c, const Seperator *sep)
{
@@ -110,6 +111,7 @@ void command_show(Client *c, const Seperator *sep)
Cmd{.cmd = "zone_loot", .u = "zone_loot", .fn = ShowZoneLoot, .a = {"#viewzoneloot"}},
Cmd{.cmd = "zone_points", .u = "zone_points", .fn = ShowZonePoints, .a = {"#showzonepoints"}},
Cmd{.cmd = "zone_status", .u = "zone_status", .fn = ShowZoneStatus, .a = {"#zonestatus"}},
Cmd{.cmd = "zone_variables", .u = "zone_variables", .fn = ShowZoneVariables},
};
// Check for arguments
-5
View File
@@ -81,11 +81,6 @@ void ShowZoneData(Client *c, const Seperator *sep)
DialogueWindow::TableCell(std::to_string(zone->newzone_data.time_type))
);
popup_table += DialogueWindow::TableRow(
DialogueWindow::TableCell("Time Type") +
DialogueWindow::TableCell(std::to_string(zone->newzone_data.time_type))
);
popup_table += DialogueWindow::TableRow(
DialogueWindow::TableCell("Experience Multiplier") +
DialogueWindow::TableCell(
+16
View File
@@ -0,0 +1,16 @@
#include "../../client.h"
#include "../../zone.h"
extern Zone* zone;
void ShowZoneVariables(Client *c, const Seperator *sep)
{
if (!zone->GetVariables().empty()) {
c->Message(Chat::White, "Zone Variables:");
for (auto &key: zone->GetVariables()) {
c->Message(Chat::White, fmt::format("{}: {}", key, zone->GetVariable(key)).c_str());
}
} else {
c->Message(Chat::White, "No zone variables set.");
}
}
+55 -13
View File
@@ -6,8 +6,18 @@ extern WorldServer worldserver;
void command_zoneshutdown(Client *c, const Seperator *sep)
{
const int arguments = sep->argnum;
if (!arguments) {
c->Message(Chat::White, "Usage: #zoneshutdown [Zone ID|Zone Short Name]");
if (arguments < 2) {
c->Message(Chat::White, "Usage: #zoneshutdown instance [Instance ID]");
c->Message(Chat::White, "Usage: #zoneshutdown zone [Zone ID|Zone Short Name]");
return;
}
bool is_instance = !strcasecmp(sep->arg[1], "instance");
bool is_zone = !strcasecmp(sep->arg[1], "zone");
if (!is_instance && !is_zone) {
c->Message(Chat::White, "Usage: #zoneshutdown instance [Instance ID]");
c->Message(Chat::White, "Usage: #zoneshutdown zone [Zone ID|Zone Short Name]");
return;
}
@@ -16,23 +26,55 @@ void command_zoneshutdown(Client *c, const Seperator *sep)
return;
}
const uint32 zone_id = sep->IsNumber(1) ? Strings::ToUnsignedInt(sep->arg[1]) : ZoneID(sep->arg[1]);
uint32 zone_id = 0;
uint16 instance_id = 0;
std::string message = "";
if (!zone_id) {
c->Message(
Chat::White,
fmt::format(
"Zone '{}' does not exist.",
sep->arg[1]
).c_str()
);
return;
if (is_instance) {
instance_id = sep->IsNumber(2) ? Strings::ToUnsignedInt(sep->arg[2]) : 0;
if (!database.CheckInstanceExists(instance_id)) {
c->Message(
Chat::White,
fmt::format(
"Instance ID '{}' does not exist.",
instance_id
).c_str()
);
return;
}
message = fmt::format("Instance ID {}", instance_id);
} else if (is_zone) {
zone_id = sep->IsNumber(2) ? Strings::ToUnsignedInt(sep->arg[2]) : ZoneID(sep->arg[2]);
if (!zone_id) {
c->Message(
Chat::White,
fmt::format(
"Zone '{}' does not exist.",
sep->arg[1]
).c_str()
);
return;
}
message = fmt::format("{} (ID {})", ZoneLongName(zone_id), zone_id);
}
c->Message(
Chat::White,
fmt::format(
"Attempting to shut down {}.",
message
).c_str()
);
auto pack = new ServerPacket(ServerOP_ZoneShutdown, sizeof(ServerZoneStateChange_Struct));
auto *s = (ServerZoneStateChange_Struct *) pack->pBuffer;
s->zone_id = zone_id;
s->zone_id = zone_id;
s->instance_id = instance_id;
strn0cpy(s->admin_name, c->GetName(), sizeof(s->admin_name));
+1 -1
View File
@@ -195,7 +195,7 @@ void Client::SendGuildList()
std::stringstream ss;
cereal::BinaryOutputArchive ar(ss);
ar(guilds_list);
{ ar(guilds_list); }
uint32 packet_size = ss.str().length();
+1 -1
View File
@@ -108,7 +108,7 @@ const NPCType *Horse::BuildHorseType(uint16 spell_id)
n->npc_id = 0;
n->loottable_id = 0;
n->texture = e.texture;
n->helmtexture = e.texture;
n->helmtexture = e.helmtexture == -1 ? e.texture : e.helmtexture;
n->runspeed = e.mountspeed;
n->light = 0;
n->STR = 75;
+2 -2
View File
@@ -763,7 +763,7 @@ void Client::DropItem(int16 slot_id, bool recurse)
int i = 0;
if (player_event_logs.IsEventEnabled(PlayerEvent::DROPPED_ITEM)) {
if (inst && player_event_logs.IsEventEnabled(PlayerEvent::DROPPED_ITEM)) {
auto e = PlayerEvent::DroppedItemEvent{
.item_id = inst->GetID(),
.augment_1_id = inst->GetAugmentItemID(0),
@@ -1655,7 +1655,7 @@ bool Client::SwapItem(MoveItem_Struct* move_in) {
DeleteItemInInventory(EQ::invslot::slotCursor, 0, true);
if (player_event_logs.IsEventEnabled(PlayerEvent::ITEM_DESTROY)) {
if (test_inst && player_event_logs.IsEventEnabled(PlayerEvent::ITEM_DESTROY)) {
auto e = PlayerEvent::DestroyItemEvent{
.item_id = test_inst->GetItem()->ID,
.item_name = test_inst->GetItem()->Name,
+6 -1
View File
@@ -23,6 +23,11 @@ void NPC::AddLootTable(uint32 loottable_id, bool is_global)
return;
}
if (m_resumed_from_zone_suspend) {
LogZoneState("NPC [{}] is resuming from zone suspend, skipping", GetCleanName());
return;
}
if (!is_global) {
m_loot_copper = 0;
m_loot_silver = 0;
@@ -277,7 +282,7 @@ void NPC::AddLootDrop(
)
{
if (m_resumed_from_zone_suspend) {
LogZoneState("NPC [{}] is resuming from zone suspend, skipping AddItem", GetCleanName());
LogZoneState("NPC [{}] is resuming from zone suspend, skipping", GetCleanName());
return;
}
+21
View File
@@ -3496,6 +3496,24 @@ std::string Lua_Client::GetAccountBucketRemaining(std::string bucket_name)
return self->GetAccountBucketRemaining(bucket_name);
}
void Lua_Client::GrantNameChange()
{
Lua_Safe_Call_Void();
self->GrantNameChange();
}
bool Lua_Client::IsNameChangeAllowed()
{
Lua_Safe_Call_Bool();
return self->IsNameChangeAllowed();
}
bool Lua_Client::ClearNameChange()
{
Lua_Safe_Call_Bool();
return self->ClearNameChange();
}
std::string Lua_Client::GetBandolierName(uint8 bandolier_slot)
{
Lua_Safe_Call_String();
@@ -3635,6 +3653,7 @@ luabind::scope lua_register_client() {
.def("CashReward", &Lua_Client::CashReward)
.def("ChangeLastName", (void(Lua_Client::*)(std::string))&Lua_Client::ChangeLastName)
.def("GrantPetNameChange", &Lua_Client::GrantPetNameChange)
.def("ClearNameChange", &Lua_Client::ClearNameChange)
.def("CharacterID", (uint32(Lua_Client::*)(void))&Lua_Client::CharacterID)
.def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob))&Lua_Client::CheckIncreaseSkill)
.def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob,int))&Lua_Client::CheckIncreaseSkill)
@@ -3851,6 +3870,7 @@ luabind::scope lua_register_client() {
.def("GrantAllAAPoints", (void(Lua_Client::*)(uint8,bool))&Lua_Client::GrantAllAAPoints)
.def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int))&Lua_Client::GrantAlternateAdvancementAbility)
.def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int, bool))&Lua_Client::GrantAlternateAdvancementAbility)
.def("GrantNameChange", &Lua_Client::GrantNameChange)
.def("GuildID", (uint32(Lua_Client::*)(void))&Lua_Client::GuildID)
.def("GuildRank", (int(Lua_Client::*)(void))&Lua_Client::GuildRank)
.def("HasAugmentEquippedByID", (bool(Lua_Client::*)(uint32))&Lua_Client::HasAugmentEquippedByID)
@@ -3881,6 +3901,7 @@ luabind::scope lua_register_client() {
.def("IsInAGuild", (bool(Lua_Client::*)(void))&Lua_Client::IsInAGuild)
.def("IsLD", (bool(Lua_Client::*)(void))&Lua_Client::IsLD)
.def("IsMedding", (bool(Lua_Client::*)(void))&Lua_Client::IsMedding)
.def("IsNameChangeAllowed", &Lua_Client::IsNameChangeAllowed)
.def("IsRaidGrouped", (bool(Lua_Client::*)(void))&Lua_Client::IsRaidGrouped)
.def("IsSitting", (bool(Lua_Client::*)(void))&Lua_Client::IsSitting)
.def("IsStanding", (bool(Lua_Client::*)(void))&Lua_Client::IsStanding)
+4
View File
@@ -609,6 +609,10 @@ public:
void ShowZoneShardMenu();
void GrantPetNameChange();
void GrantNameChange();
bool IsNameChangeAllowed();
bool ClearNameChange();
Lua_Expedition CreateExpedition(luabind::object expedition_info);
Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players);
Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool disable_messages);

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